Updates for 0.9.5
[debian/openrocket] / src / net / sf / openrocket / gui / plot / PlotConfiguration.java
1 package net.sf.openrocket.gui.plot;
2
3 import java.util.ArrayList;
4 import java.util.EnumSet;
5 import java.util.List;
6 import java.util.Set;
7
8 import net.sf.openrocket.simulation.FlightDataBranch;
9 import net.sf.openrocket.simulation.FlightEvent;
10 import net.sf.openrocket.simulation.FlightDataBranch.Type;
11 import net.sf.openrocket.unit.Unit;
12 import net.sf.openrocket.util.BugException;
13 import net.sf.openrocket.util.MathUtil;
14 import net.sf.openrocket.util.Pair;
15
16
17 public class PlotConfiguration implements Cloneable {
18         
19         public static final PlotConfiguration[] DEFAULT_CONFIGURATIONS;
20         static {
21                 ArrayList<PlotConfiguration> configs = new ArrayList<PlotConfiguration>();
22                 PlotConfiguration config;
23                 
24                 config = new PlotConfiguration("Vertical motion vs. time");
25                 config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE, 0);
26                 config.addPlotDataType(FlightDataBranch.TYPE_VELOCITY_Z);
27                 config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_Z);
28                 config.setEvent(FlightEvent.Type.IGNITION, true);
29                 config.setEvent(FlightEvent.Type.BURNOUT, true);
30                 config.setEvent(FlightEvent.Type.APOGEE, true);
31                 config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
32                 config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
33                 config.setEvent(FlightEvent.Type.GROUND_HIT, true);
34                 configs.add(config);
35                 
36                 config = new PlotConfiguration("Total motion vs. time");
37                 config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE, 0);
38                 config.addPlotDataType(FlightDataBranch.TYPE_VELOCITY_TOTAL);
39                 config.addPlotDataType(FlightDataBranch.TYPE_ACCELERATION_TOTAL);
40                 config.setEvent(FlightEvent.Type.IGNITION, true);
41                 config.setEvent(FlightEvent.Type.BURNOUT, true);
42                 config.setEvent(FlightEvent.Type.APOGEE, true);
43                 config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
44                 config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
45                 config.setEvent(FlightEvent.Type.GROUND_HIT, true);
46                 configs.add(config);
47                 
48                 config = new PlotConfiguration("Flight side profile", FlightDataBranch.TYPE_POSITION_X);
49                 config.addPlotDataType(FlightDataBranch.TYPE_ALTITUDE);
50                 config.setEvent(FlightEvent.Type.IGNITION, true);
51                 config.setEvent(FlightEvent.Type.BURNOUT, true);
52                 config.setEvent(FlightEvent.Type.APOGEE, true);
53                 config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
54                 config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
55                 config.setEvent(FlightEvent.Type.GROUND_HIT, true);
56                 configs.add(config);
57
58                 config = new PlotConfiguration("Stability vs. time");
59                 config.addPlotDataType(FlightDataBranch.TYPE_STABILITY, 0);
60                 config.addPlotDataType(FlightDataBranch.TYPE_CP_LOCATION, 1);
61                 config.addPlotDataType(FlightDataBranch.TYPE_CG_LOCATION, 1);
62                 config.setEvent(FlightEvent.Type.IGNITION, true);
63                 config.setEvent(FlightEvent.Type.BURNOUT, true);
64                 config.setEvent(FlightEvent.Type.APOGEE, true);
65                 config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
66                 config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
67                 config.setEvent(FlightEvent.Type.GROUND_HIT, true);
68                 configs.add(config);
69                 
70                 config = new PlotConfiguration("Drag coefficients vs. Mach number", 
71                                 FlightDataBranch.TYPE_MACH_NUMBER);
72                 config.addPlotDataType(FlightDataBranch.TYPE_DRAG_COEFF, 0);
73                 config.addPlotDataType(FlightDataBranch.TYPE_FRICTION_DRAG_COEFF, 0);
74                 config.addPlotDataType(FlightDataBranch.TYPE_BASE_DRAG_COEFF, 0);
75                 config.addPlotDataType(FlightDataBranch.TYPE_PRESSURE_DRAG_COEFF, 0);
76                 configs.add(config);
77
78                 config = new PlotConfiguration("Roll characteristics");
79                 config.addPlotDataType(FlightDataBranch.TYPE_ROLL_RATE, 0);
80                 config.addPlotDataType(FlightDataBranch.TYPE_ROLL_MOMENT_COEFF, 1);
81                 config.addPlotDataType(FlightDataBranch.TYPE_ROLL_FORCING_COEFF, 1);
82                 config.addPlotDataType(FlightDataBranch.TYPE_ROLL_DAMPING_COEFF, 1);
83                 config.setEvent(FlightEvent.Type.IGNITION, true);
84                 config.setEvent(FlightEvent.Type.LAUNCHROD, true);
85                 config.setEvent(FlightEvent.Type.BURNOUT, true);
86                 config.setEvent(FlightEvent.Type.APOGEE, true);
87                 config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
88                 config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
89                 config.setEvent(FlightEvent.Type.GROUND_HIT, true);
90                 configs.add(config);
91
92                 config = new PlotConfiguration("Angle of attack and orientation vs. time");
93                 config.addPlotDataType(FlightDataBranch.TYPE_AOA, 0);
94                 config.addPlotDataType(FlightDataBranch.TYPE_ORIENTATION_PHI);
95                 config.addPlotDataType(FlightDataBranch.TYPE_ORIENTATION_THETA);
96                 config.setEvent(FlightEvent.Type.IGNITION, true);
97                 config.setEvent(FlightEvent.Type.BURNOUT, true);
98                 config.setEvent(FlightEvent.Type.APOGEE, true);
99                 config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
100                 config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
101                 config.setEvent(FlightEvent.Type.GROUND_HIT, true);
102                 configs.add(config);
103
104                 config = new PlotConfiguration("Simulation time step and computation time");
105                 config.addPlotDataType(FlightDataBranch.TYPE_TIME_STEP);
106                 config.addPlotDataType(FlightDataBranch.TYPE_COMPUTATION_TIME);
107                 config.setEvent(FlightEvent.Type.IGNITION, true);
108                 config.setEvent(FlightEvent.Type.BURNOUT, true);
109                 config.setEvent(FlightEvent.Type.APOGEE, true);
110                 config.setEvent(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, true);
111                 config.setEvent(FlightEvent.Type.STAGE_SEPARATION, true);
112                 config.setEvent(FlightEvent.Type.GROUND_HIT, true);
113                 configs.add(config);
114
115                 DEFAULT_CONFIGURATIONS = configs.toArray(new PlotConfiguration[0]);
116         }
117         
118         
119         
120         /** Bonus given for the first type being on the first axis */
121         private static final double BONUS_FIRST_TYPE_ON_FIRST_AXIS = 1.0;
122
123         /**
124          * Bonus given if the first axis includes zero (to prefer first axis having zero over 
125          * the others) 
126          */
127         private static final double BONUS_FIRST_AXIS_HAS_ZERO = 2.0;
128         
129         /** Bonus given for a common zero point on left and right axes. */
130         private static final double BONUS_COMMON_ZERO = 40.0;
131         
132         /** Bonus given for only using a single axis. */
133         private static final double BONUS_ONLY_ONE_AXIS = 50.0;
134         
135         
136         private static final double INCLUDE_ZERO_DISTANCE = 0.3;  // 30% of total range
137         
138         
139
140         /** The data types to be plotted. */
141         private ArrayList<FlightDataBranch.Type> plotDataTypes = new ArrayList<FlightDataBranch.Type>();
142         
143         private ArrayList<Unit> plotDataUnits = new ArrayList<Unit>();
144         
145         /** The corresponding Axis on which they will be plotted, or null to auto-select. */
146         private ArrayList<Integer> plotDataAxes = new ArrayList<Integer>();
147
148         private EnumSet<FlightEvent.Type> events = EnumSet.noneOf(FlightEvent.Type.class);
149         
150         /** The domain (x) axis. */
151         private FlightDataBranch.Type domainAxisType = null;
152         private Unit domainAxisUnit = null;
153         
154         
155         /** All available axes. */
156         private final int axesCount;
157         private ArrayList<Axis> allAxes = new ArrayList<Axis>();
158         
159         
160         
161         private String name = null;
162         
163         
164         
165         public PlotConfiguration() {
166                 this(null, FlightDataBranch.TYPE_TIME);
167         }
168         
169         public PlotConfiguration(String name) {
170                 this(name, FlightDataBranch.TYPE_TIME);
171         }
172         
173         public PlotConfiguration(String name, FlightDataBranch.Type domainType) {
174                 this.name = name;
175                 // Two axes
176                 allAxes.add(new Axis());
177                 allAxes.add(new Axis());
178                 axesCount = 2;
179                 
180                 setDomainAxisType(domainType);
181         }
182         
183         
184         
185         
186         
187         public FlightDataBranch.Type getDomainAxisType() {
188                 return domainAxisType;
189         }
190         
191         public void setDomainAxisType(FlightDataBranch.Type type) {
192                 boolean setUnit;
193                 
194                 if (domainAxisType != null  &&  domainAxisType.getUnitGroup() == type.getUnitGroup())
195                         setUnit = false;
196                 else
197                         setUnit = true;
198                 
199                 domainAxisType = type;
200                 if (setUnit)
201                         domainAxisUnit = domainAxisType.getUnitGroup().getDefaultUnit();
202         }
203         
204         public Unit getDomainAxisUnit() {
205                 return domainAxisUnit;
206         }
207         
208         public void setDomainAxisUnit(Unit u) {
209                 if (!domainAxisType.getUnitGroup().contains(u)) {
210                         throw new IllegalArgumentException("Setting unit "+u+" to type "+domainAxisType);
211                 }
212                 domainAxisUnit = u;
213         }
214         
215         
216         
217         public void addPlotDataType(FlightDataBranch.Type type) {
218                 plotDataTypes.add(type);
219                 plotDataUnits.add(type.getUnitGroup().getDefaultUnit());
220                 plotDataAxes.add(-1);
221         }
222
223         public void addPlotDataType(FlightDataBranch.Type type, int axis) {
224                 if (axis >= axesCount) {
225                         throw new IllegalArgumentException("Axis index too large");
226                 }
227                 plotDataTypes.add(type);
228                 plotDataUnits.add(type.getUnitGroup().getDefaultUnit());
229                 plotDataAxes.add(axis);
230         }
231
232
233         
234         
235         public void setPlotDataType(int index, FlightDataBranch.Type type) {
236                 FlightDataBranch.Type origType = plotDataTypes.get(index);
237                 plotDataTypes.set(index, type);
238                 
239                 if (origType.getUnitGroup() != type.getUnitGroup()) {
240                         plotDataUnits.set(index, type.getUnitGroup().getDefaultUnit());
241                 }
242         }
243         
244         public void setPlotDataUnit(int index, Unit unit) {
245                 if (!plotDataTypes.get(index).getUnitGroup().contains(unit)) {
246                         throw new IllegalArgumentException("Attempting to set unit "+unit+" to group "
247                                         + plotDataTypes.get(index).getUnitGroup());
248                 }
249                 plotDataUnits.set(index, unit);
250         }
251         
252         public void setPlotDataAxis(int index, int axis) {
253                 if (axis >= axesCount) {
254                         throw new IllegalArgumentException("Axis index too large");
255                 }
256                 plotDataAxes.set(index, axis);
257         }
258         
259         
260         public void setPlotDataType(int index, FlightDataBranch.Type type, Unit unit, int axis) {
261                 if (axis >= axesCount) {
262                         throw new IllegalArgumentException("Axis index too large");
263                 }
264                 plotDataTypes.set(index, type);
265                 plotDataUnits.set(index, unit);
266                 plotDataAxes.set(index, axis);
267         }
268         
269         public void removePlotDataType(int index) {
270                 plotDataTypes.remove(index);
271                 plotDataUnits.remove(index);
272                 plotDataAxes.remove(index);
273         }
274         
275         
276         
277         public FlightDataBranch.Type getType (int index) {
278                 return plotDataTypes.get(index);
279         }
280         public Unit getUnit(int index) {
281                 return plotDataUnits.get(index);
282         }
283         public int getAxis(int index) {
284                 return plotDataAxes.get(index);
285         }
286         
287         public int getTypeCount() {
288                 return plotDataTypes.size();
289         }
290         
291         
292         /// Events
293         
294         public Set<FlightEvent.Type> getActiveEvents() {
295                 return (Set<FlightEvent.Type>) events.clone();
296         }
297         
298         public void setEvent(FlightEvent.Type type, boolean active) {
299                 if (active) {
300                         events.add(type);
301                 } else {
302                         events.remove(type);
303                 }
304         }
305         
306         public boolean isEventActive(FlightEvent.Type type) {
307                 return events.contains(type);
308         }
309         
310         
311         
312         
313         
314         public List<Axis> getAllAxes() {
315                 List<Axis> list = new ArrayList<Axis>();
316                 list.addAll(allAxes);
317                 return list;
318         }
319         
320         
321         public String getName() {
322                 return name;
323         }
324         
325         public void setName(String name) {
326                 this.name = name;
327         }
328         
329         /**
330          * Returns the name of this PlotConfiguration.
331          */
332         @Override
333         public String toString() {
334                 return name;
335         }
336         
337         
338         
339         /**
340          * Find the best combination of the auto-selectable axes.
341          * 
342          * @return      a new PlotConfiguration with the best fitting auto-selected axes and
343          *                      axes ranges selected.
344          */
345         public PlotConfiguration fillAutoAxes(FlightDataBranch data) {
346                 PlotConfiguration config = recursiveFillAutoAxes(data).getU();
347                 System.out.println("BEST FOUND, fitting");
348                 config.fitAxes(data);
349                 return config;
350         }
351         
352         
353         
354         
355         /**
356          * Recursively search for the best combination of the auto-selectable axes.
357          * This is a brute-force search method.
358          * 
359          * @return      a new PlotConfiguration with the best fitting auto-selected axes and
360          *                      axes ranges selected, and the goodness value
361          */
362         private Pair<PlotConfiguration, Double> recursiveFillAutoAxes(FlightDataBranch data) {
363                 
364                 // Create copy to fill in
365                 PlotConfiguration copy = this.clone();
366                 
367                 int autoindex; 
368                 for (autoindex=0; autoindex < plotDataAxes.size(); autoindex++) {
369                         if (plotDataAxes.get(autoindex) < 0)
370                                 break;
371                 }
372                 
373                 
374                 if (autoindex >= plotDataAxes.size()) {
375                         // All axes have been assigned, just return since we are already the best
376                         return new Pair<PlotConfiguration, Double>(copy, copy.getGoodnessValue(data));
377                 }
378                 
379                 
380                 // Set the auto-selected index one at a time and choose the best one
381                 PlotConfiguration best = null;
382                 double bestValue = Double.NEGATIVE_INFINITY;
383                 for (int i=0; i < axesCount; i++) {
384                         copy.plotDataAxes.set(autoindex, i);
385                         Pair<PlotConfiguration, Double> result = copy.recursiveFillAutoAxes(data);
386                         if (result.getV() > bestValue) {
387                                 best = result.getU();
388                                 bestValue = result.getV();
389                         }
390                 }
391                 
392                 return new Pair<PlotConfiguration, Double>(best, bestValue);
393         }
394         
395         
396         
397         
398         
399         /**
400          * Fit the axes to hold the provided data.  All of the plotDataAxis elements must
401          * be non-negative.
402          * <p>
403          * NOTE: This method assumes that only two axes are used.
404          */
405         protected void fitAxes(FlightDataBranch data) {
406                 
407                 // Reset axes
408                 for (Axis a: allAxes) {
409                         a.reset();
410                 }
411                 
412                 // Add full range to the axes
413                 int length = plotDataTypes.size();
414                 for (int i=0; i<length; i++) {
415                         FlightDataBranch.Type type = plotDataTypes.get(i);
416                         Unit unit = plotDataUnits.get(i);
417                         int index = plotDataAxes.get(i);
418                         if (index < 0) {
419                                 throw new IllegalStateException("fitAxes called with auto-selected axis");
420                         }
421                         Axis axis = allAxes.get(index);
422                         
423                         double min = unit.toUnit(data.getMinimum(type));
424                         double max = unit.toUnit(data.getMaximum(type));
425
426                         axis.addBound(min);
427                         axis.addBound(max);
428                 }
429                 
430                 // Ensure non-zero (or NaN) range, add a few percent range, include zero if it is close
431                 for (Axis a: allAxes) {
432                         if (MathUtil.equals(a.getMinValue(), a.getMaxValue())) {
433                                 a.addBound(a.getMinValue()-1);
434                                 a.addBound(a.getMaxValue()+1);
435                         }
436                         
437                         double addition = a.getRangeLength() * 0.03;
438                         a.addBound(a.getMinValue() - addition);
439                         a.addBound(a.getMaxValue() + addition);
440                         
441                         double dist;
442                         dist = Math.min(Math.abs(a.getMinValue()), Math.abs(a.getMaxValue()));
443                         if (dist <= a.getRangeLength() * INCLUDE_ZERO_DISTANCE) {
444                                 a.addBound(0);
445                         }
446                 }
447
448                 
449                 // Check whether to use a common zero
450                 Axis left = allAxes.get(0);
451                 Axis right = allAxes.get(1);
452                 
453                 if (left.getMinValue() > 0 || left.getMaxValue() < 0 ||
454                                 right.getMinValue() > 0 || right.getMaxValue() < 0 ||
455                                 Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue()))
456                         return;
457                 
458                 
459                 
460                 //// Compute common zero
461                 // TODO: MEDIUM: This algorithm may require tweaking
462
463                 double min1 = left.getMinValue();
464                 double max1 = left.getMaxValue();
465                 double min2 = right.getMinValue();
466                 double max2 = right.getMaxValue();
467                 
468                 // Calculate and round scaling factor
469                 double scale = Math.max(left.getRangeLength(), right.getRangeLength()) /
470                                                 Math.min(left.getRangeLength(), right.getRangeLength());
471                 
472                 System.out.println("Scale: "+scale);
473                 
474                 scale = roundScale(scale);
475                 if (right.getRangeLength() > left.getRangeLength()) {
476                         scale = 1/scale;
477                 }
478                 System.out.println("Rounded scale: " + scale);
479
480                 // Scale right axis, enlarge axes if necessary and scale back
481                 min2 *= scale;
482                 max2 *= scale;
483                 min1 = Math.min(min1, min2);
484                 min2 = min1;
485                 max1 = Math.max(max1, max2);
486                 max2 = max1;
487                 min2 /= scale;
488                 max2 /= scale;
489                 
490                 
491                 
492                 // Scale to unit length
493 //              double scale1 = left.getRangeLength();
494 //              double scale2 = right.getRangeLength();
495 //              
496 //              double min1 = left.getMinValue() / scale1;
497 //              double max1 = left.getMaxValue() / scale1;
498 //              double min2 = right.getMinValue() / scale2;
499 //              double max2 = right.getMaxValue() / scale2;
500 //              
501 //              // Combine unit ranges
502 //              min1 = MathUtil.min(min1, min2);
503 //              min2 = min1;
504 //              max1 = MathUtil.max(max1, max2);
505 //              max2 = max1;
506 //              
507 //              // Scale up
508 //              min1 *= scale1;
509 //              max1 *= scale1;
510 //              min2 *= scale2;
511 //              max2 *= scale2;
512 //              
513 //              // Compute common scale
514 //              double range1 = max1-min1;
515 //              double range2 = max2-min2;
516 //              
517 //              double scale = MathUtil.max(range1, range2) / MathUtil.min(range1, range2);
518 //              double roundScale = roundScale(scale);
519 //
520 //              if (range2 < range1) {
521 //                      if (roundScale < scale) {
522 //                              min2 = min1 / roundScale;
523 //                              max2 = max1 / roundScale;
524 //                      } else {
525 //                              min1 = min2 * roundScale;
526 //                              max1 = max2 * roundScale;
527 //                      }
528 //              } else {
529 //                      if (roundScale > scale) {
530 //                              min2 = min1 * roundScale;
531 //                              max2 = max1 * roundScale;
532 //                      } else {
533 //                              min1 = min2 / roundScale;
534 //                              max1 = max2 / roundScale;
535 //                      }
536 //              }
537                 
538                 // Apply scale
539                 left.addBound(min1);
540                 left.addBound(max1);
541                 right.addBound(min2);
542                 right.addBound(max2);
543                 
544         }
545         
546         
547
548         private double roundScale(double scale) {
549                 double mul = 1;
550                 while (scale >= 10) {
551                         scale /= 10;
552                         mul *= 10;
553                 }
554                 while (scale < 1) {
555                         scale *= 10;
556                         mul /= 10;
557                 }
558                 
559                 // 1 2 4 5 10
560                 
561                 if (scale > 7.5) {
562                         scale = 10;
563                 } else if (scale > 4.5) {
564                         scale = 5;
565                 } else if (scale > 3) {
566                         scale = 4;
567                 } else if (scale > 1.5) {
568                         scale = 2;
569                 } else {
570                         scale = 1;
571                 }
572                 return scale*mul;
573         }
574         
575         
576         
577         private double roundScaleUp(double scale) {
578                 double mul = 1;
579                 while (scale >= 10) {
580                         scale /= 10;
581                         mul *= 10;
582                 }
583                 while (scale < 1) {
584                         scale *= 10;
585                         mul /= 10;
586                 }
587                 
588                 if (scale > 5) {
589                         scale = 10;
590                 } else if (scale > 4) {
591                         scale = 5;
592                 } else if (scale > 2) {
593                         scale = 4;
594                 } else if (scale > 1) {
595                         scale = 2;
596                 } else {
597                         scale = 1;
598                 }
599                 return scale*mul;
600         }
601         
602
603         private double roundScaleDown(double scale) {
604                 double mul = 1;
605                 while (scale >= 10) {
606                         scale /= 10;
607                         mul *= 10;
608                 }
609                 while (scale < 1) {
610                         scale *= 10;
611                         mul /= 10;
612                 }
613                 
614                 if (scale > 5) {
615                         scale = 5;
616                 } else if (scale > 4) {
617                         scale = 4;
618                 } else if (scale > 2) {
619                         scale = 2;
620                 } else {
621                         scale = 1;
622                 }
623                 return scale*mul;
624         }
625         
626         
627         
628         /**
629          * Fits the axis ranges to the data and returns the "goodness value" of this 
630          * selection of axes.  All plotDataAxis elements must be non-null.
631          * <p>
632          * NOTE: This method assumes that all data can fit into the axes ranges and
633          * that only two axes are used.
634          *
635          * @return      a "goodness value", the larger the better.
636          */
637         protected double getGoodnessValue(FlightDataBranch data) {
638                 double goodness = 0;
639                 int length = plotDataTypes.size();
640                 
641                 // Fit the axes ranges to the data
642                 fitAxes(data);
643                 
644                 /*
645                  * Calculate goodness of ranges.  100 points is given if the values fill the
646                  * entire range, 0 if they fill none of it.
647                  */
648                 for (int i = 0; i < length; i++) {
649                         FlightDataBranch.Type type = plotDataTypes.get(i);
650                         Unit unit = plotDataUnits.get(i);
651                         int index = plotDataAxes.get(i);
652                         if (index < 0) {
653                                 throw new IllegalStateException("getGoodnessValue called with auto-selected axis");
654                         }
655                         Axis axis = allAxes.get(index);
656                         
657                         double min = unit.toUnit(data.getMinimum(type));
658                         double max = unit.toUnit(data.getMaximum(type));
659                         if (Double.isNaN(min) || Double.isNaN(max))
660                                 continue;
661                         if (MathUtil.equals(min, max))
662                                 continue;
663                         
664                         double d = (max-min) / axis.getRangeLength();
665                         d = Math.sqrt(d);  // Prioritize small ranges
666                         goodness += d * 100.0;
667                 }
668                 
669                 
670                 /*
671                  * Add extra points for specific things.
672                  */
673
674                 // A little for the first type being on the first axis
675                 if (plotDataAxes.get(0) == 0)
676                         goodness += BONUS_FIRST_TYPE_ON_FIRST_AXIS;
677                 
678                 // A little bonus if the first axis contains zero
679                 Axis left = allAxes.get(0);
680                 if (left.getMinValue() <= 0 && left.getMaxValue() >= 0)
681                         goodness += BONUS_FIRST_AXIS_HAS_ZERO;
682                 
683                 // A boost if a common zero was used in the ranging
684                 Axis right = allAxes.get(1);
685                 if (left.getMinValue() <= 0 &&  left.getMaxValue() >= 0 &&
686                                 right.getMinValue() <= 0 && right.getMaxValue() >= 0)
687                         goodness += BONUS_COMMON_ZERO;
688                 
689                 // A boost if only one axis is used
690                 if (Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue()))
691                         goodness += BONUS_ONLY_ONE_AXIS;
692                 
693                 return goodness;
694         }
695         
696         
697         
698         /**
699          * Reset the units of this configuration to the default units. Returns this
700          * PlotConfiguration.
701          * 
702          * @return   this PlotConfiguration.
703          */
704         public PlotConfiguration resetUnits() {
705                 for (int i=0; i < plotDataTypes.size(); i++) {
706                         plotDataUnits.set(i, plotDataTypes.get(i).getUnitGroup().getDefaultUnit());
707                 }
708                 return this;
709         }
710         
711         
712         
713         
714         @SuppressWarnings("unchecked")
715         @Override
716         public PlotConfiguration clone() {
717                 try {
718                         
719                         PlotConfiguration copy = (PlotConfiguration) super.clone();
720                         
721                         // Shallow-clone all immutable lists
722                         copy.plotDataTypes = (ArrayList<Type>) this.plotDataTypes.clone();
723                         copy.plotDataAxes = (ArrayList<Integer>) this.plotDataAxes.clone();
724                         copy.plotDataUnits = (ArrayList<Unit>) this.plotDataUnits.clone();
725                         copy.events = this.events.clone();
726                         
727                         // Deep-clone all Axis since they are mutable
728                         copy.allAxes = new ArrayList<Axis>();
729                         for (Axis a: this.allAxes) {
730                                 copy.allAxes.add(a.clone());
731                         }
732                         
733                         return copy;
734                         
735                         
736                 } catch (CloneNotSupportedException e) {
737                         throw new BugException("BUG! Could not clone().");
738                 }
739         }
740         
741 }