create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / dialogs / optimization / OptimizationPlotDialog.java
1 package net.sf.openrocket.gui.dialogs.optimization;
2
3 import java.awt.Color;
4 import java.awt.Paint;
5 import java.awt.Window;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.util.ArrayList;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Map;
12
13 import javax.swing.BorderFactory;
14 import javax.swing.JButton;
15 import javax.swing.JDialog;
16 import javax.swing.JLabel;
17 import javax.swing.JPanel;
18
19 import net.miginfocom.swing.MigLayout;
20 import net.sf.openrocket.gui.components.StyledLabel;
21 import net.sf.openrocket.gui.util.GUIUtil;
22 import net.sf.openrocket.l10n.Translator;
23 import net.sf.openrocket.logging.LogHelper;
24 import net.sf.openrocket.optimization.general.Point;
25 import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
26 import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
27 import net.sf.openrocket.startup.Application;
28 import net.sf.openrocket.unit.Unit;
29 import net.sf.openrocket.unit.UnitGroup;
30 import net.sf.openrocket.unit.Value;
31 import net.sf.openrocket.util.LinearInterpolator;
32 import net.sf.openrocket.util.MathUtil;
33
34 import org.jfree.chart.ChartFactory;
35 import org.jfree.chart.ChartPanel;
36 import org.jfree.chart.JFreeChart;
37 import org.jfree.chart.annotations.XYBoxAnnotation;
38 import org.jfree.chart.annotations.XYLineAnnotation;
39 import org.jfree.chart.annotations.XYPointerAnnotation;
40 import org.jfree.chart.axis.AxisLocation;
41 import org.jfree.chart.axis.NumberAxis;
42 import org.jfree.chart.labels.CustomXYToolTipGenerator;
43 import org.jfree.chart.plot.PlotOrientation;
44 import org.jfree.chart.plot.XYPlot;
45 import org.jfree.chart.renderer.PaintScale;
46 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
47 import org.jfree.chart.renderer.xy.XYShapeRenderer;
48 import org.jfree.chart.title.PaintScaleLegend;
49 import org.jfree.data.xy.DefaultXYZDataset;
50 import org.jfree.data.xy.XYSeries;
51 import org.jfree.data.xy.XYSeriesCollection;
52 import org.jfree.ui.RectangleEdge;
53 import org.jfree.ui.TextAnchor;
54
55 /**
56  * A class that plots the path of an optimization.
57  * 
58  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
59  */
60 public class OptimizationPlotDialog extends JDialog {
61         private static final LogHelper log = Application.getLogger();
62         private static final Translator trans = Application.getTranslator();
63         
64
65         private static final LinearInterpolator RED = new LinearInterpolator(
66                         new double[] { 0.0, 1.0 }, new double[] { 0.0, 1.0 }
67                         );
68         private static final LinearInterpolator GREEN = new LinearInterpolator(
69                         new double[] { 0.0, 1.0 }, new double[] { 0.0, 0.0 }
70                         );
71         private static final LinearInterpolator BLUE = new LinearInterpolator(
72                         new double[] { 0.0, 1.0 }, new double[] { 1.0, 0.0 }
73                         );
74         
75         private static final Color OUT_OF_DOMAIN_COLOR = Color.BLACK;
76         
77         private static final Color PATH_COLOR = new Color(220, 0, 0);
78         
79         
80         public OptimizationPlotDialog(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
81                         List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit, Window parent) {
82                 super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
83                 
84
85                 JPanel panel = new JPanel(new MigLayout("fill"));
86                 
87                 ChartPanel chart;
88                 if (modifiers.size() == 1) {
89                         chart = create1DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
90                 } else if (modifiers.size() == 2) {
91                         chart = create2DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
92                 } else {
93                         throw new IllegalArgumentException("Invalid dimensionality, dim=" + modifiers.size());
94                 }
95                 chart.setBorder(BorderFactory.createLineBorder(Color.BLACK));
96                 panel.add(chart, "span, grow, wrap para");
97                 
98
99                 JLabel label = new StyledLabel(trans.get("lbl.zoomInstructions"), -2);
100                 panel.add(label, "");
101                 
102
103                 JButton close = new JButton(trans.get("button.close"));
104                 close.addActionListener(new ActionListener() {
105                         @Override
106                         public void actionPerformed(ActionEvent e) {
107                                 OptimizationPlotDialog.this.setVisible(false);
108                         }
109                 });
110                 panel.add(close, "right");
111                 
112
113                 this.add(panel);
114                 
115                 GUIUtil.setDisposableDialogOptions(this, close);
116                 GUIUtil.rememberWindowSize(this);
117         }
118         
119         
120
121         /**
122          * Create a 1D plot of the optimization path.
123          */
124         private ChartPanel create1DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
125                         List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
126                 
127                 SimulationModifier modX = modifiers.get(0);
128                 Unit xUnit = modX.getUnitGroup().getDefaultUnit();
129                 Unit yUnit = parameter.getUnitGroup().getDefaultUnit();
130                 
131                 // Create the optimization path (with autosort)
132                 XYSeries series = new XYSeries(trans.get("plot1d.series"), true, true);
133                 List<String> tooltips = new ArrayList<String>();
134                 for (Point p : evaluations.keySet()) {
135                         FunctionEvaluationData data = evaluations.get(p);
136                         if (data != null) {
137                                 if (data.getParameterValue() != null) {
138                                         Value[] state = data.getState();
139                                         series.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(data.getParameterValue().getValue()));
140                                         tooltips.add(getTooltip(data, parameter));
141                                 }
142                         } else {
143                                 log.error("Could not find evaluation data for point " + p);
144                         }
145                 }
146                 
147
148                 String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
149                 String yLabel = parameter.getName() + " / " + yUnit.getUnit();
150                 
151                 JFreeChart chart = ChartFactory.createXYLineChart(
152                                 trans.get("plot1d.title"),
153                                 xLabel,
154                                 yLabel,
155                                 null,
156                                 PlotOrientation.VERTICAL,
157                                 false, // Legend
158                                 true, // Tooltips
159                                 false); // Urls
160                 
161
162                 // Set the scale of the plot to the limits
163                 double x1 = xUnit.toUnit(modX.getMinValue());
164                 double x2 = xUnit.toUnit(modX.getMaxValue());
165                 
166                 if (x1 < x2 - 0.0001) {
167                         log.debug("Setting 1D plot domain axis x1=" + x1 + " x2=" + x2);
168                         chart.getXYPlot().getDomainAxis().setRange(x1, x2);
169                 } else {
170                         log.warn("1D plot domain singular x1=" + x1 + " x2=" + x2 + ", not setting");
171                 }
172                 
173                 // Add lines to show optimization limits
174                 XYLineAnnotation line = new XYLineAnnotation(x1, -1e19, x1, 1e19);
175                 chart.getXYPlot().addAnnotation(line);
176                 line = new XYLineAnnotation(x2, -1e19, x2, 1e19);
177                 chart.getXYPlot().addAnnotation(line);
178                 
179                 // Mark the optimum point
180                 Point optimum = path.get(path.size() - 1);
181                 FunctionEvaluationData data = evaluations.get(optimum);
182                 if (data != null) {
183                         if (data.getParameterValue() != null) {
184                                 Value[] state = data.getState();
185                                 double x = xUnit.toUnit(state[0].getValue());
186                                 double y = yUnit.toUnit(data.getParameterValue().getValue());
187                                 
188                                 XYPointerAnnotation text = new XYPointerAnnotation(trans.get("plot.label.optimum"),
189                                                 x, y, Math.PI / 2);
190                                 text.setTextAnchor(TextAnchor.TOP_LEFT);
191                                 chart.getXYPlot().addAnnotation(text);
192                         }
193                 } else {
194                         log.error("Could not find evaluation data for point " + optimum);
195                 }
196                 
197
198                 XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
199                 lineRenderer.setBaseShapesVisible(true);
200                 lineRenderer.setSeriesShapesFilled(0, false);
201                 //lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
202                 lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
203                 lineRenderer.setSeriesPaint(0, PATH_COLOR);
204                 lineRenderer.setUseOutlinePaint(true);
205                 CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
206                 tooltipGenerator.addToolTipSeries(tooltips);
207                 lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
208                 
209                 XYPlot plot = chart.getXYPlot();
210                 
211                 plot.setDataset(0, new XYSeriesCollection(series));
212                 plot.setRenderer(lineRenderer);
213                 
214
215
216                 return new ChartPanel(chart);
217         }
218         
219         /**
220          * Create a 2D plot of the optimization path.
221          */
222         private ChartPanel create2DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
223                         List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
224                 
225                 Unit parameterUnit = parameter.getUnitGroup().getDefaultUnit();
226                 
227                 SimulationModifier modX = modifiers.get(0);
228                 SimulationModifier modY = modifiers.get(1);
229                 
230                 Unit xUnit = modX.getUnitGroup().getDefaultUnit();
231                 Unit yUnit = modY.getUnitGroup().getDefaultUnit();
232                 
233                 // Create the optimization path dataset
234                 XYSeries pathSeries = new XYSeries(trans.get("plot2d.path"), false, true);
235                 List<String> pathTooltips = new ArrayList<String>();
236                 for (Point p : path) {
237                         FunctionEvaluationData data = evaluations.get(p);
238                         if (data != null) {
239                                 Value[] state = data.getState();
240                                 pathSeries.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(state[1].getValue()));
241                                 pathTooltips.add(getTooltip(data, parameter));
242                         } else {
243                                 log.error("Could not find evaluation data for point " + p);
244                         }
245                 }
246                 
247
248                 // Create evaluations dataset
249                 double min = Double.POSITIVE_INFINITY;
250                 double max = Double.NEGATIVE_INFINITY;
251                 double[][] evals = new double[3][evaluations.size()];
252                 List<String> evalTooltips = new ArrayList<String>();
253                 
254                 Iterator<FunctionEvaluationData> iterator = evaluations.values().iterator();
255                 for (int i = 0; i < evaluations.size(); i++) {
256                         FunctionEvaluationData data = iterator.next();
257                         Value param = data.getParameterValue();
258                         double value;
259                         if (param != null) {
260                                 value = parameterUnit.toUnit(data.getParameterValue().getValue());
261                         } else {
262                                 value = Double.NaN;
263                         }
264                         
265                         Value[] state = data.getState();
266                         evals[0][i] = xUnit.toUnit(state[0].getValue());
267                         evals[1][i] = yUnit.toUnit(state[1].getValue());
268                         evals[2][i] = value;
269                         
270                         if (value < min) {
271                                 min = value;
272                         }
273                         if (value > max) {
274                                 max = value;
275                         }
276                         
277                         evalTooltips.add(getTooltip(data, parameter));
278                 }
279                 DefaultXYZDataset evalDataset = new DefaultXYZDataset();
280                 evalDataset.addSeries(trans.get("plot2d.evals"), evals);
281                 
282
283
284                 String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
285                 String yLabel = modY.getRelatedObject().toString() + ": " + modY.getName() + " / " + yUnit.getUnit();
286                 
287                 JFreeChart chart = ChartFactory.createXYLineChart(
288                                 trans.get("plot2d.title"),
289                                 xLabel,
290                                 yLabel,
291                                 null,
292                                 //evalDataset,
293                                 PlotOrientation.VERTICAL,
294                                 true, // Legend
295                                 true, // Tooltips
296                                 false); // Urls
297                 
298
299                 // Set the scale of the plot to the limits
300                 double x1 = xUnit.toUnit(modX.getMinValue());
301                 double x2 = xUnit.toUnit(modX.getMaxValue());
302                 double y1 = yUnit.toUnit(modY.getMinValue());
303                 double y2 = yUnit.toUnit(modY.getMaxValue());
304                 
305                 if (x1 < x2 - 0.0001) {
306                         log.debug("Setting 2D plot domain axis to x1=" + x1 + " x2=" + x2);
307                         chart.getXYPlot().getDomainAxis().setRange(x1, x2);
308                 } else {
309                         log.warn("2D plot has singular domain axis: x1=" + x1 + " x2=" + x2);
310                 }
311                 
312                 if (y1 < y2 - 0.0001) {
313                         log.debug("Setting 2D plot range axis to y1=" + y1 + " y2=" + y2);
314                         chart.getXYPlot().getRangeAxis().setRange(y1, y2);
315                 } else {
316                         log.warn("2D plot has singular range axis: y1=" + y1 + " y2=" + y2);
317                 }
318                 
319                 XYBoxAnnotation box = new XYBoxAnnotation(x1, y1, x2, y2);
320                 chart.getXYPlot().addAnnotation(box);
321                 
322                 int n = pathSeries.getItemCount();
323                 XYPointerAnnotation text = new XYPointerAnnotation(trans.get("plot.label.optimum"),
324                                 (Double) pathSeries.getX(n - 1), (Double) pathSeries.getY(n - 1), -Math.PI / 5);
325                 text.setTextAnchor(TextAnchor.BASELINE_LEFT);
326                 chart.getXYPlot().addAnnotation(text);
327                 
328
329                 if (min < max - 0.0001) {
330                         log.debug("Setting gradient scale range to min=" + min + " max=" + max);
331                 } else {
332                         log.warn("2D plot has singular gradient scale, resetting to (0,1): min=" + min + " max=" + max);
333                         min = 0;
334                         max = 1;
335                 }
336                 
337                 PaintScale paintScale = new GradientScale(min, max);
338                 
339                 XYShapeRenderer shapeRenderer = new XYShapeRenderer();
340                 shapeRenderer.setPaintScale(paintScale);
341                 shapeRenderer.setUseFillPaint(true);
342                 CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
343                 tooltipGenerator.addToolTipSeries(evalTooltips);
344                 shapeRenderer.setBaseToolTipGenerator(tooltipGenerator);
345                 
346
347                 shapeRenderer.getLegendItem(0, 0);
348                 
349
350                 XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
351                 lineRenderer.setBaseShapesVisible(true);
352                 lineRenderer.setSeriesShapesFilled(0, false);
353                 lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
354                 lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
355                 lineRenderer.setSeriesPaint(0, PATH_COLOR);
356                 lineRenderer.setUseOutlinePaint(true);
357                 tooltipGenerator = new CustomXYToolTipGenerator();
358                 tooltipGenerator.addToolTipSeries(pathTooltips);
359                 lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
360                 
361
362                 XYPlot plot = chart.getXYPlot();
363                 
364                 plot.setDataset(0, new XYSeriesCollection(pathSeries));
365                 plot.setRenderer(lineRenderer);
366                 
367                 plot.setDataset(1, evalDataset);
368                 plot.setRenderer(1, shapeRenderer);
369                 
370
371                 // Add value scale
372                 NumberAxis numberAxis = new NumberAxis(parameter.getName() + " / " + parameterUnit.getUnit());
373                 PaintScaleLegend scale = new PaintScaleLegend(paintScale, numberAxis);
374                 scale.setPosition(RectangleEdge.RIGHT);
375                 scale.setMargin(4.0D, 4.0D, 40.0D, 4.0D);
376                 scale.setAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
377                 chart.addSubtitle(scale);
378                 
379
380                 return new ChartPanel(chart);
381         }
382         
383         
384
385         private String getTooltip(FunctionEvaluationData data, OptimizableParameter parameter) {
386                 String ttip = "<html>";
387                 if (data.getParameterValue() != null) {
388                         ttip += parameter.getName() + ": " +
389                                         parameter.getUnitGroup().getDefaultUnit().toStringUnit(data.getParameterValue().getValue());
390                         ttip += "<br>";
391                 }
392                 if (data.getDomainReference() != null) {
393                         ttip += trans.get("plot.ttip.stability") + " " + data.getDomainReference();
394                 }
395                 return ttip;
396         }
397         
398         private class GradientScale implements PaintScale {
399                 
400                 private final double min;
401                 private final double max;
402                 
403                 public GradientScale(double min, double max) {
404                         this.min = min;
405                         this.max = max;
406                 }
407                 
408                 @Override
409                 public Paint getPaint(double value) {
410                         if (Double.isNaN(value)) {
411                                 return OUT_OF_DOMAIN_COLOR;
412                         }
413                         
414                         value = MathUtil.map(value, min, max, 0.0, 1.0);
415                         value = MathUtil.clamp(value, 0.0, 1.0);
416                         
417                         float r = (float) RED.getValue(value);
418                         float g = (float) GREEN.getValue(value);
419                         float b = (float) BLUE.getValue(value);
420                         
421                         return new Color(r, g, b);
422                 }
423                 
424                 @Override
425                 public double getLowerBound() {
426                         return min;
427                 }
428                 
429                 @Override
430                 public double getUpperBound() {
431                         return max;
432                 }
433         }
434         
435 }