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