added svn:ignores
[debian/openrocket] / 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.simulation.FlightDataBranch;
8 import net.sf.openrocket.simulation.FlightDataType;
9 import net.sf.openrocket.simulation.FlightEvent;
10 import net.sf.openrocket.unit.Unit;
11 import net.sf.openrocket.util.ArrayList;
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(FlightDataType.TYPE_ALTITUDE, 0);
26                 config.addPlotDataType(FlightDataType.TYPE_VELOCITY_Z);
27                 config.addPlotDataType(FlightDataType.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(FlightDataType.TYPE_ALTITUDE, 0);
38                 config.addPlotDataType(FlightDataType.TYPE_VELOCITY_TOTAL);
39                 config.addPlotDataType(FlightDataType.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", FlightDataType.TYPE_POSITION_X);
49                 config.addPlotDataType(FlightDataType.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(FlightDataType.TYPE_STABILITY, 0);
60                 config.addPlotDataType(FlightDataType.TYPE_CP_LOCATION, 1);
61                 config.addPlotDataType(FlightDataType.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                                 FlightDataType.TYPE_MACH_NUMBER);
72                 config.addPlotDataType(FlightDataType.TYPE_DRAG_COEFF, 0);
73                 config.addPlotDataType(FlightDataType.TYPE_FRICTION_DRAG_COEFF, 0);
74                 config.addPlotDataType(FlightDataType.TYPE_BASE_DRAG_COEFF, 0);
75                 config.addPlotDataType(FlightDataType.TYPE_PRESSURE_DRAG_COEFF, 0);
76                 configs.add(config);
77                 
78                 config = new PlotConfiguration("Roll characteristics");
79                 config.addPlotDataType(FlightDataType.TYPE_ROLL_RATE, 0);
80                 config.addPlotDataType(FlightDataType.TYPE_ROLL_MOMENT_COEFF, 1);
81                 config.addPlotDataType(FlightDataType.TYPE_ROLL_FORCING_COEFF, 1);
82                 config.addPlotDataType(FlightDataType.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(FlightDataType.TYPE_AOA, 0);
94                 config.addPlotDataType(FlightDataType.TYPE_ORIENTATION_PHI);
95                 config.addPlotDataType(FlightDataType.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(FlightDataType.TYPE_TIME_STEP);
106                 config.addPlotDataType(FlightDataType.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<FlightDataType> plotDataTypes = new ArrayList<FlightDataType>();
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 FlightDataType 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, FlightDataType.TYPE_TIME);
167         }
168         
169         public PlotConfiguration(String name) {
170                 this(name, FlightDataType.TYPE_TIME);
171         }
172         
173         public PlotConfiguration(String name, FlightDataType 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 FlightDataType getDomainAxisType() {
188                 return domainAxisType;
189         }
190         
191         public void setDomainAxisType(FlightDataType 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(FlightDataType type) {
218                 plotDataTypes.add(type);
219                 plotDataUnits.add(type.getUnitGroup().getDefaultUnit());
220                 plotDataAxes.add(-1);
221         }
222         
223         public void addPlotDataType(FlightDataType 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, FlightDataType type) {
236                 FlightDataType 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, FlightDataType 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 FlightDataType getType(int index) {
278                 return plotDataTypes.get(index);
279         }
280         
281         public Unit getUnit(int index) {
282                 return plotDataUnits.get(index);
283         }
284         
285         public int getAxis(int index) {
286                 return plotDataAxes.get(index);
287         }
288         
289         public int getTypeCount() {
290                 return plotDataTypes.size();
291         }
292         
293         
294         /// Events
295         
296         public Set<FlightEvent.Type> getActiveEvents() {
297                 return events.clone();
298         }
299         
300         public void setEvent(FlightEvent.Type type, boolean active) {
301                 if (active) {
302                         events.add(type);
303                 } else {
304                         events.remove(type);
305                 }
306         }
307         
308         public boolean isEventActive(FlightEvent.Type type) {
309                 return events.contains(type);
310         }
311         
312         
313
314
315
316         public List<Axis> getAllAxes() {
317                 List<Axis> list = new ArrayList<Axis>();
318                 list.addAll(allAxes);
319                 return list;
320         }
321         
322         
323         public String getName() {
324                 return name;
325         }
326         
327         public void setName(String name) {
328                 this.name = name;
329         }
330         
331         /**
332          * Returns the name of this PlotConfiguration.
333          */
334         @Override
335         public String toString() {
336                 return name;
337         }
338         
339         
340
341         /**
342          * Find the best combination of the auto-selectable axes.
343          * 
344          * @return      a new PlotConfiguration with the best fitting auto-selected axes and
345          *                      axes ranges selected.
346          */
347         public PlotConfiguration fillAutoAxes(FlightDataBranch data) {
348                 PlotConfiguration config = recursiveFillAutoAxes(data).getU();
349                 System.out.println("BEST FOUND, fitting");
350                 config.fitAxes(data);
351                 return config;
352         }
353         
354         
355
356
357         /**
358          * Recursively search for the best combination of the auto-selectable axes.
359          * This is a brute-force search method.
360          * 
361          * @return      a new PlotConfiguration with the best fitting auto-selected axes and
362          *                      axes ranges selected, and the goodness value
363          */
364         private Pair<PlotConfiguration, Double> recursiveFillAutoAxes(FlightDataBranch data) {
365                 
366                 // Create copy to fill in
367                 PlotConfiguration copy = this.clone();
368                 
369                 int autoindex;
370                 for (autoindex = 0; autoindex < plotDataAxes.size(); autoindex++) {
371                         if (plotDataAxes.get(autoindex) < 0)
372                                 break;
373                 }
374                 
375
376                 if (autoindex >= plotDataAxes.size()) {
377                         // All axes have been assigned, just return since we are already the best
378                         return new Pair<PlotConfiguration, Double>(copy, copy.getGoodnessValue(data));
379                 }
380                 
381
382                 // Set the auto-selected index one at a time and choose the best one
383                 PlotConfiguration best = null;
384                 double bestValue = Double.NEGATIVE_INFINITY;
385                 for (int i = 0; i < axesCount; i++) {
386                         copy.plotDataAxes.set(autoindex, i);
387                         Pair<PlotConfiguration, Double> result = copy.recursiveFillAutoAxes(data);
388                         if (result.getV() > bestValue) {
389                                 best = result.getU();
390                                 bestValue = result.getV();
391                         }
392                 }
393                 
394                 return new Pair<PlotConfiguration, Double>(best, bestValue);
395         }
396         
397         
398
399
400
401         /**
402          * Fit the axes to hold the provided data.  All of the plotDataAxis elements must
403          * be non-negative.
404          * <p>
405          * NOTE: This method assumes that only two axes are used.
406          */
407         protected void fitAxes(FlightDataBranch data) {
408                 
409                 // Reset axes
410                 for (Axis a : allAxes) {
411                         a.reset();
412                 }
413                 
414                 // Add full range to the axes
415                 int length = plotDataTypes.size();
416                 for (int i = 0; i < length; i++) {
417                         FlightDataType type = plotDataTypes.get(i);
418                         Unit unit = plotDataUnits.get(i);
419                         int index = plotDataAxes.get(i);
420                         if (index < 0) {
421                                 throw new IllegalStateException("fitAxes called with auto-selected axis");
422                         }
423                         Axis axis = allAxes.get(index);
424                         
425                         double min = unit.toUnit(data.getMinimum(type));
426                         double max = unit.toUnit(data.getMaximum(type));
427                         
428                         axis.addBound(min);
429                         axis.addBound(max);
430                 }
431                 
432                 // Ensure non-zero (or NaN) range, add a few percent range, include zero if it is close
433                 for (Axis a : allAxes) {
434                         if (MathUtil.equals(a.getMinValue(), a.getMaxValue())) {
435                                 a.addBound(a.getMinValue() - 1);
436                                 a.addBound(a.getMaxValue() + 1);
437                         }
438                         
439                         double addition = a.getRangeLength() * 0.03;
440                         a.addBound(a.getMinValue() - addition);
441                         a.addBound(a.getMaxValue() + addition);
442                         
443                         double dist;
444                         dist = Math.min(Math.abs(a.getMinValue()), Math.abs(a.getMaxValue()));
445                         if (dist <= a.getRangeLength() * INCLUDE_ZERO_DISTANCE) {
446                                 a.addBound(0);
447                         }
448                 }
449                 
450
451                 // Check whether to use a common zero
452                 Axis left = allAxes.get(0);
453                 Axis right = allAxes.get(1);
454                 
455                 if (left.getMinValue() > 0 || left.getMaxValue() < 0 ||
456                                 right.getMinValue() > 0 || right.getMaxValue() < 0 ||
457                                 Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue()))
458                         return;
459                 
460
461
462                 //// Compute common zero
463                 // TODO: MEDIUM: This algorithm may require tweaking
464                 
465                 double min1 = left.getMinValue();
466                 double max1 = left.getMaxValue();
467                 double min2 = right.getMinValue();
468                 double max2 = right.getMaxValue();
469                 
470                 // Calculate and round scaling factor
471                 double scale = Math.max(left.getRangeLength(), right.getRangeLength()) /
472                                                 Math.min(left.getRangeLength(), right.getRangeLength());
473                 
474                 System.out.println("Scale: " + scale);
475                 
476                 scale = roundScale(scale);
477                 if (right.getRangeLength() > left.getRangeLength()) {
478                         scale = 1 / scale;
479                 }
480                 System.out.println("Rounded scale: " + scale);
481                 
482                 // Scale right axis, enlarge axes if necessary and scale back
483                 min2 *= scale;
484                 max2 *= scale;
485                 min1 = Math.min(min1, min2);
486                 min2 = min1;
487                 max1 = Math.max(max1, max2);
488                 max2 = max1;
489                 min2 /= scale;
490                 max2 /= scale;
491                 
492
493
494                 // Scale to unit length
495                 //              double scale1 = left.getRangeLength();
496                 //              double scale2 = right.getRangeLength();
497                 //              
498                 //              double min1 = left.getMinValue() / scale1;
499                 //              double max1 = left.getMaxValue() / scale1;
500                 //              double min2 = right.getMinValue() / scale2;
501                 //              double max2 = right.getMaxValue() / scale2;
502                 //              
503                 //              // Combine unit ranges
504                 //              min1 = MathUtil.min(min1, min2);
505                 //              min2 = min1;
506                 //              max1 = MathUtil.max(max1, max2);
507                 //              max2 = max1;
508                 //              
509                 //              // Scale up
510                 //              min1 *= scale1;
511                 //              max1 *= scale1;
512                 //              min2 *= scale2;
513                 //              max2 *= scale2;
514                 //              
515                 //              // Compute common scale
516                 //              double range1 = max1-min1;
517                 //              double range2 = max2-min2;
518                 //              
519                 //              double scale = MathUtil.max(range1, range2) / MathUtil.min(range1, range2);
520                 //              double roundScale = roundScale(scale);
521                 //
522                 //              if (range2 < range1) {
523                 //                      if (roundScale < scale) {
524                 //                              min2 = min1 / roundScale;
525                 //                              max2 = max1 / roundScale;
526                 //                      } else {
527                 //                              min1 = min2 * roundScale;
528                 //                              max1 = max2 * roundScale;
529                 //                      }
530                 //              } else {
531                 //                      if (roundScale > scale) {
532                 //                              min2 = min1 * roundScale;
533                 //                              max2 = max1 * roundScale;
534                 //                      } else {
535                 //                              min1 = min2 / roundScale;
536                 //                              max1 = max2 / roundScale;
537                 //                      }
538                 //              }
539                 
540                 // Apply scale
541                 left.addBound(min1);
542                 left.addBound(max1);
543                 right.addBound(min2);
544                 right.addBound(max2);
545                 
546         }
547         
548         
549
550         private double roundScale(double scale) {
551                 double mul = 1;
552                 while (scale >= 10) {
553                         scale /= 10;
554                         mul *= 10;
555                 }
556                 while (scale < 1) {
557                         scale *= 10;
558                         mul /= 10;
559                 }
560                 
561                 // 1 2 4 5 10
562                 
563                 if (scale > 7.5) {
564                         scale = 10;
565                 } else if (scale > 4.5) {
566                         scale = 5;
567                 } else if (scale > 3) {
568                         scale = 4;
569                 } else if (scale > 1.5) {
570                         scale = 2;
571                 } else {
572                         scale = 1;
573                 }
574                 return scale * mul;
575         }
576         
577         
578
579         private double roundScaleUp(double scale) {
580                 double mul = 1;
581                 while (scale >= 10) {
582                         scale /= 10;
583                         mul *= 10;
584                 }
585                 while (scale < 1) {
586                         scale *= 10;
587                         mul /= 10;
588                 }
589                 
590                 if (scale > 5) {
591                         scale = 10;
592                 } else if (scale > 4) {
593                         scale = 5;
594                 } else if (scale > 2) {
595                         scale = 4;
596                 } else if (scale > 1) {
597                         scale = 2;
598                 } else {
599                         scale = 1;
600                 }
601                 return scale * mul;
602         }
603         
604         
605         private double roundScaleDown(double scale) {
606                 double mul = 1;
607                 while (scale >= 10) {
608                         scale /= 10;
609                         mul *= 10;
610                 }
611                 while (scale < 1) {
612                         scale *= 10;
613                         mul /= 10;
614                 }
615                 
616                 if (scale > 5) {
617                         scale = 5;
618                 } else if (scale > 4) {
619                         scale = 4;
620                 } else if (scale > 2) {
621                         scale = 2;
622                 } else {
623                         scale = 1;
624                 }
625                 return scale * mul;
626         }
627         
628         
629
630         /**
631          * Fits the axis ranges to the data and returns the "goodness value" of this 
632          * selection of axes.  All plotDataAxis elements must be non-null.
633          * <p>
634          * NOTE: This method assumes that all data can fit into the axes ranges and
635          * that only two axes are used.
636          *
637          * @return      a "goodness value", the larger the better.
638          */
639         protected double getGoodnessValue(FlightDataBranch data) {
640                 double goodness = 0;
641                 int length = plotDataTypes.size();
642                 
643                 // Fit the axes ranges to the data
644                 fitAxes(data);
645                 
646                 /*
647                  * Calculate goodness of ranges.  100 points is given if the values fill the
648                  * entire range, 0 if they fill none of it.
649                  */
650                 for (int i = 0; i < length; i++) {
651                         FlightDataType type = plotDataTypes.get(i);
652                         Unit unit = plotDataUnits.get(i);
653                         int index = plotDataAxes.get(i);
654                         if (index < 0) {
655                                 throw new IllegalStateException("getGoodnessValue called with auto-selected axis");
656                         }
657                         Axis axis = allAxes.get(index);
658                         
659                         double min = unit.toUnit(data.getMinimum(type));
660                         double max = unit.toUnit(data.getMaximum(type));
661                         if (Double.isNaN(min) || Double.isNaN(max))
662                                 continue;
663                         if (MathUtil.equals(min, max))
664                                 continue;
665                         
666                         double d = (max - min) / axis.getRangeLength();
667                         d = Math.sqrt(d); // Prioritize small ranges
668                         goodness += d * 100.0;
669                 }
670                 
671
672                 /*
673                  * Add extra points for specific things.
674                  */
675
676                 // A little for the first type being on the first axis
677                 if (plotDataAxes.get(0) == 0)
678                         goodness += BONUS_FIRST_TYPE_ON_FIRST_AXIS;
679                 
680                 // A little bonus if the first axis contains zero
681                 Axis left = allAxes.get(0);
682                 if (left.getMinValue() <= 0 && left.getMaxValue() >= 0)
683                         goodness += BONUS_FIRST_AXIS_HAS_ZERO;
684                 
685                 // A boost if a common zero was used in the ranging
686                 Axis right = allAxes.get(1);
687                 if (left.getMinValue() <= 0 && left.getMaxValue() >= 0 &&
688                                 right.getMinValue() <= 0 && right.getMaxValue() >= 0)
689                         goodness += BONUS_COMMON_ZERO;
690                 
691                 // A boost if only one axis is used
692                 if (Double.isNaN(left.getMinValue()) || Double.isNaN(right.getMinValue()))
693                         goodness += BONUS_ONLY_ONE_AXIS;
694                 
695                 return goodness;
696         }
697         
698         
699
700         /**
701          * Reset the units of this configuration to the default units. Returns this
702          * PlotConfiguration.
703          * 
704          * @return   this PlotConfiguration.
705          */
706         public PlotConfiguration resetUnits() {
707                 for (int i = 0; i < plotDataTypes.size(); i++) {
708                         plotDataUnits.set(i, plotDataTypes.get(i).getUnitGroup().getDefaultUnit());
709                 }
710                 return this;
711         }
712         
713         
714
715
716         @Override
717         public PlotConfiguration clone() {
718                 try {
719                         
720                         PlotConfiguration copy = (PlotConfiguration) super.clone();
721                         
722                         // Shallow-clone all immutable lists
723                         copy.plotDataTypes = this.plotDataTypes.clone();
724                         copy.plotDataAxes = this.plotDataAxes.clone();
725                         copy.plotDataUnits = this.plotDataUnits.clone();
726                         copy.events = this.events.clone();
727                         
728                         // Deep-clone all Axis since they are mutable
729                         copy.allAxes = new ArrayList<Axis>();
730                         for (Axis a : this.allAxes) {
731                                 copy.allAxes.add(a.clone());
732                         }
733                         
734                         return copy;
735                         
736
737                 } catch (CloneNotSupportedException e) {
738                         throw new BugException("BUG! Could not clone().");
739                 }
740         }
741         
742 }