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