Updated French translations
[debian/openrocket] / src / net / sf / openrocket / gui / plot / PlotDialog.java
1 package net.sf.openrocket.gui.plot;
2
3 import java.awt.AlphaComposite;
4 import java.awt.BasicStroke;
5 import java.awt.Color;
6 import java.awt.Composite;
7 import java.awt.Font;
8 import java.awt.Graphics2D;
9 import java.awt.Image;
10 import java.awt.Window;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.awt.geom.Line2D;
14 import java.awt.geom.Point2D;
15 import java.awt.geom.Rectangle2D;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24
25 import javax.imageio.ImageIO;
26 import javax.swing.BorderFactory;
27 import javax.swing.JButton;
28 import javax.swing.JCheckBox;
29 import javax.swing.JDialog;
30 import javax.swing.JLabel;
31 import javax.swing.JPanel;
32
33 import net.miginfocom.swing.MigLayout;
34 import net.sf.openrocket.document.Simulation;
35 import net.sf.openrocket.gui.components.StyledLabel;
36 import net.sf.openrocket.l10n.Translator;
37 import net.sf.openrocket.simulation.FlightDataBranch;
38 import net.sf.openrocket.simulation.FlightDataType;
39 import net.sf.openrocket.simulation.FlightEvent;
40 import net.sf.openrocket.startup.Application;
41 import net.sf.openrocket.unit.Unit;
42 import net.sf.openrocket.unit.UnitGroup;
43 import net.sf.openrocket.util.BugException;
44 import net.sf.openrocket.util.GUIUtil;
45 import net.sf.openrocket.util.MathUtil;
46 import net.sf.openrocket.util.Prefs;
47
48 import org.jfree.chart.ChartFactory;
49 import org.jfree.chart.ChartPanel;
50 import org.jfree.chart.JFreeChart;
51 import org.jfree.chart.annotations.XYImageAnnotation;
52 import org.jfree.chart.axis.NumberAxis;
53 import org.jfree.chart.axis.ValueAxis;
54 import org.jfree.chart.plot.Marker;
55 import org.jfree.chart.plot.PlotOrientation;
56 import org.jfree.chart.plot.ValueMarker;
57 import org.jfree.chart.plot.XYPlot;
58 import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
59 import org.jfree.chart.title.TextTitle;
60 import org.jfree.data.Range;
61 import org.jfree.data.xy.XYSeries;
62 import org.jfree.data.xy.XYSeriesCollection;
63 import org.jfree.text.TextUtilities;
64 import org.jfree.ui.LengthAdjustmentType;
65 import org.jfree.ui.RectangleAnchor;
66 import org.jfree.ui.TextAnchor;
67
68 public class PlotDialog extends JDialog {
69         
70         private static final float PLOT_STROKE_WIDTH = 1.5f;
71         private static final Translator trans = Application.getTranslator();
72         
73         private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
74         private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
75                         new HashMap<FlightEvent.Type, Color>();
76         static {
77                 EVENT_COLORS.put(FlightEvent.Type.LAUNCH, new Color(255, 0, 0));
78                 EVENT_COLORS.put(FlightEvent.Type.LIFTOFF, new Color(0, 80, 196));
79                 EVENT_COLORS.put(FlightEvent.Type.LAUNCHROD, new Color(0, 100, 80));
80                 EVENT_COLORS.put(FlightEvent.Type.IGNITION, new Color(230, 130, 15));
81                 EVENT_COLORS.put(FlightEvent.Type.BURNOUT, new Color(80, 55, 40));
82                 EVENT_COLORS.put(FlightEvent.Type.EJECTION_CHARGE, new Color(80, 55, 40));
83                 EVENT_COLORS.put(FlightEvent.Type.STAGE_SEPARATION, new Color(80, 55, 40));
84                 EVENT_COLORS.put(FlightEvent.Type.APOGEE, new Color(15, 120, 15));
85                 EVENT_COLORS.put(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, new Color(0, 0, 128));
86                 EVENT_COLORS.put(FlightEvent.Type.GROUND_HIT, new Color(0, 0, 0));
87                 EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0));
88         }
89         
90         private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
91                         new HashMap<FlightEvent.Type, Image>();
92         static {
93                 loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
94                 loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
95                 loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
96                 loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
97                 loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
98                 loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
99                 loadImage(FlightEvent.Type.STAGE_SEPARATION,
100                                 "pix/eventicons/event-stage-separation.png");
101                 loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
102                 loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
103                                 "pix/eventicons/event-recovery-device-deployment.png");
104                 loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
105                 loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
106         }
107         
108         private static void loadImage(FlightEvent.Type type, String file) {
109                 InputStream is;
110                 
111                 is = ClassLoader.getSystemResourceAsStream(file);
112                 if (is == null) {
113                         System.out.println("ERROR: File " + file + " not found!");
114                         return;
115                 }
116                 
117                 try {
118                         Image image = ImageIO.read(is);
119                         EVENT_IMAGES.put(type, image);
120                 } catch (IOException ignore) {
121                         ignore.printStackTrace();
122                 }
123         }
124         
125         
126
127
128         private final List<ModifiedXYItemRenderer> renderers =
129                         new ArrayList<ModifiedXYItemRenderer>();
130         
131         private PlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
132                 //// Flight data plot
133                 super(parent, trans.get("PlotDialog.title.Flightdataplot"));
134                 this.setModalityType(ModalityType.DOCUMENT_MODAL);
135                 
136                 final boolean initialShowPoints = Prefs.getBoolean(Prefs.PLOT_SHOW_POINTS, false);
137                 
138
139                 // Fill the auto-selections
140                 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
141                 PlotConfiguration filled = config.fillAutoAxes(branch);
142                 List<Axis> axes = filled.getAllAxes();
143                 
144
145                 // Create the data series for both axes
146                 XYSeriesCollection[] data = new XYSeriesCollection[2];
147                 data[0] = new XYSeriesCollection();
148                 data[1] = new XYSeriesCollection();
149                 
150
151                 // Get the domain axis type
152                 final FlightDataType domainType = filled.getDomainAxisType();
153                 final Unit domainUnit = filled.getDomainAxisUnit();
154                 if (domainType == null) {
155                         throw new IllegalArgumentException("Domain axis type not specified.");
156                 }
157                 List<Double> x = branch.get(domainType);
158                 
159
160                 // Get plot length (ignore trailing NaN's)
161                 int typeCount = filled.getTypeCount();
162                 int dataLength = 0;
163                 for (int i = 0; i < typeCount; i++) {
164                         FlightDataType type = filled.getType(i);
165                         List<Double> y = branch.get(type);
166                         
167                         for (int j = dataLength; j < y.size(); j++) {
168                                 if (!Double.isNaN(y.get(j)) && !Double.isInfinite(y.get(j)))
169                                         dataLength = j;
170                         }
171                 }
172                 dataLength = Math.min(dataLength, x.size());
173                 
174
175                 // Create the XYSeries objects from the flight data and store into the collections
176                 String[] axisLabel = new String[2];
177                 for (int i = 0; i < typeCount; i++) {
178                         // Get info
179                         FlightDataType type = filled.getType(i);
180                         Unit unit = filled.getUnit(i);
181                         int axis = filled.getAxis(i);
182                         String name = getLabel(type, unit);
183                         
184                         // Store data in provided units
185                         List<Double> y = branch.get(type);
186                         XYSeries series = new XYSeries(name, false, true);
187                         for (int j = 0; j < dataLength; j++) {
188                                 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
189                         }
190                         data[axis].addSeries(series);
191                         
192                         // Update axis label
193                         if (axisLabel[axis] == null)
194                                 axisLabel[axis] = type.getName();
195                         else
196                                 axisLabel[axis] += "; " + type.getName();
197                 }
198                 
199
200                 // Create the chart using the factory to get all default settings
201                 JFreeChart chart = ChartFactory.createXYLineChart(
202                                 //// Simulated flight
203                                 trans.get("PlotDialog.Chart.Simulatedflight"),
204                                 null,
205                                 null,
206                                 null,
207                                 PlotOrientation.VERTICAL,
208                                 true,
209                                 true,
210                                 false
211                                 );
212                 
213                 chart.addSubtitle(new TextTitle(config.getName()));
214                 
215                 // Add the data and formatting to the plot
216                 XYPlot plot = chart.getXYPlot();
217                 int axisno = 0;
218                 for (int i = 0; i < 2; i++) {
219                         // Check whether axis has any data
220                         if (data[i].getSeriesCount() > 0) {
221                                 // Create and set axis
222                                 double min = axes.get(i).getMinValue();
223                                 double max = axes.get(i).getMaxValue();
224                                 NumberAxis axis = new PresetNumberAxis(min, max);
225                                 axis.setLabel(axisLabel[i]);
226                                 //                              axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
227                                 plot.setRangeAxis(axisno, axis);
228                                 
229                                 // Add data and map to the axis
230                                 plot.setDataset(axisno, data[i]);
231                                 ModifiedXYItemRenderer r = new ModifiedXYItemRenderer();
232                                 r.setBaseShapesVisible(initialShowPoints);
233                                 r.setBaseShapesFilled(true);
234                                 for (int j = 0; j < data[i].getSeriesCount(); j++) {
235                                         r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
236                                 }
237                                 renderers.add(r);
238                                 plot.setRenderer(axisno, r);
239                                 plot.mapDatasetToRangeAxis(axisno, axisno);
240                                 axisno++;
241                         }
242                 }
243                 
244                 plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
245                 plot.addDomainMarker(new ValueMarker(0));
246                 plot.addRangeMarker(new ValueMarker(0));
247                 
248
249
250                 // Create list of events to show (combine event too close to each other)
251                 ArrayList<Double> timeList = new ArrayList<Double>();
252                 ArrayList<String> eventList = new ArrayList<String>();
253                 ArrayList<Color> colorList = new ArrayList<Color>();
254                 ArrayList<Image> imageList = new ArrayList<Image>();
255                 
256                 HashSet<FlightEvent.Type> typeSet = new HashSet<FlightEvent.Type>();
257                 
258                 double prevTime = -100;
259                 String text = null;
260                 Color color = null;
261                 Image image = null;
262                 
263                 List<FlightEvent> events = branch.getEvents();
264                 for (int i = 0; i < events.size(); i++) {
265                         FlightEvent event = events.get(i);
266                         double t = event.getTime();
267                         FlightEvent.Type type = event.getType();
268                         
269                         if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
270                                 if (Math.abs(t - prevTime) <= 0.01) {
271                                         
272                                         if (!typeSet.contains(type)) {
273                                                 text = text + ", " + type.toString();
274                                                 color = getEventColor(type);
275                                                 image = EVENT_IMAGES.get(type);
276                                                 typeSet.add(type);
277                                         }
278                                         
279                                 } else {
280                                         
281                                         if (text != null) {
282                                                 timeList.add(prevTime);
283                                                 eventList.add(text);
284                                                 colorList.add(color);
285                                                 imageList.add(image);
286                                         }
287                                         prevTime = t;
288                                         text = type.toString();
289                                         color = getEventColor(type);
290                                         image = EVENT_IMAGES.get(type);
291                                         typeSet.clear();
292                                         typeSet.add(type);
293                                         
294                                 }
295                         }
296                 }
297                 if (text != null) {
298                         timeList.add(prevTime);
299                         eventList.add(text);
300                         colorList.add(color);
301                         imageList.add(image);
302                 }
303                 
304
305                 // Create the event markers
306                 
307                 if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
308                         
309                         // Domain time is plotted as vertical markers
310                         for (int i = 0; i < eventList.size(); i++) {
311                                 double t = timeList.get(i);
312                                 String event = eventList.get(i);
313                                 color = colorList.get(i);
314                                 
315                                 ValueMarker m = new ValueMarker(t);
316                                 m.setLabel(event);
317                                 m.setPaint(color);
318                                 m.setLabelPaint(color);
319                                 m.setAlpha(0.7f);
320                                 plot.addDomainMarker(m);
321                         }
322                         
323                 } else {
324                         
325                         // Other domains are plotted as image annotations
326                         List<Double> time = branch.get(FlightDataType.TYPE_TIME);
327                         List<Double> domain = branch.get(config.getDomainAxisType());
328                         
329                         for (int i = 0; i < eventList.size(); i++) {
330                                 final double t = timeList.get(i);
331                                 String event = eventList.get(i);
332                                 image = imageList.get(i);
333                                 
334                                 if (image == null)
335                                         continue;
336                                 
337                                 // Calculate index and interpolation position a
338                                 final double a;
339                                 int tindex = Collections.binarySearch(time, t);
340                                 if (tindex < 0) {
341                                         tindex = -tindex - 1;
342                                 }
343                                 if (tindex >= time.size()) {
344                                         // index greater than largest value in time list
345                                         tindex = time.size() - 1;
346                                         a = 0;
347                                 } else if (tindex <= 0) {
348                                         // index smaller than smallest value in time list
349                                         tindex = 0;
350                                         a = 0;
351                                 } else {
352                                         tindex--;
353                                         double t1 = time.get(tindex);
354                                         double t2 = time.get(tindex + 1);
355                                         
356                                         if ((t1 > t) || (t2 < t)) {
357                                                 throw new BugException("t1=" + t1 + " t2=" + t2 + " t=" + t);
358                                         }
359                                         
360                                         if (MathUtil.equals(t1, t2)) {
361                                                 a = 0;
362                                         } else {
363                                                 a = 1 - (t - t1) / (t2 - t1);
364                                         }
365                                 }
366                                 
367                                 double xcoord;
368                                 if (a == 0) {
369                                         xcoord = domain.get(tindex);
370                                 } else {
371                                         xcoord = a * domain.get(tindex) + (1 - a) * domain.get(tindex + 1);
372                                 }
373                                 
374                                 for (int index = 0; index < config.getTypeCount(); index++) {
375                                         FlightDataType type = config.getType(index);
376                                         List<Double> range = branch.get(type);
377                                         
378                                         // Image annotations are not supported on the right-side axis
379                                         // TODO: LOW: Can this be achieved by JFreeChart?
380                                         if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
381                                                 continue;
382                                         }
383                                         
384                                         double ycoord;
385                                         if (a == 0) {
386                                                 ycoord = range.get(tindex);
387                                         } else {
388                                                 ycoord = a * range.get(tindex) + (1 - a) * range.get(tindex + 1);
389                                         }
390                                         
391                                         // Convert units
392                                         xcoord = config.getDomainAxisUnit().toUnit(xcoord);
393                                         ycoord = config.getUnit(index).toUnit(ycoord);
394                                         
395                                         XYImageAnnotation annotation =
396                                                                 new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
397                                         annotation.setToolTipText(event);
398                                         plot.addAnnotation(annotation);
399                                 }
400                         }
401                 }
402                 
403
404                 // Create the dialog
405                 
406                 JPanel panel = new JPanel(new MigLayout("fill"));
407                 this.add(panel);
408                 
409                 ChartPanel chartPanel = new ChartPanel(chart,
410                                 false, // properties
411                                 true, // save
412                                 false, // print
413                                 true, // zoom
414                                 true); // tooltips
415                 chartPanel.setMouseWheelEnabled(true);
416                 chartPanel.setEnforceFileExtensions(true);
417                 chartPanel.setInitialDelay(500);
418                 
419                 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
420                 
421                 panel.add(chartPanel, "grow, wrap 20lp");
422                 
423                 //// Show data points
424                 final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
425                 check.setSelected(initialShowPoints);
426                 check.addActionListener(new ActionListener() {
427                         @Override
428                         public void actionPerformed(ActionEvent e) {
429                                 boolean show = check.isSelected();
430                                 Prefs.putBoolean(Prefs.PLOT_SHOW_POINTS, show);
431                                 for (ModifiedXYItemRenderer r : renderers) {
432                                         r.setBaseShapesVisible(show);
433                                 }
434                         }
435                 });
436                 panel.add(check, "split, left");
437                 
438
439                 JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
440                 panel.add(label, "gapleft para");
441                 
442
443                 panel.add(new JPanel(), "growx");
444                 
445                 //// Close button
446                 JButton button = new JButton(trans.get("dlg.but.close"));
447                 button.addActionListener(new ActionListener() {
448                         @Override
449                         public void actionPerformed(ActionEvent e) {
450                                 PlotDialog.this.dispose();
451                         }
452                 });
453                 panel.add(button, "right");
454                 
455                 this.setLocationByPlatform(true);
456                 this.pack();
457                 
458                 GUIUtil.setDisposableDialogOptions(this, button);
459         }
460         
461         private String getLabel(FlightDataType type, Unit unit) {
462                 String name = type.getName();
463                 if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
464                                 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
465                         name += " (" + unit.getUnit() + ")";
466                 return name;
467         }
468         
469         
470
471         private class PresetNumberAxis extends NumberAxis {
472                 private final double min;
473                 private final double max;
474                 
475                 public PresetNumberAxis(double min, double max) {
476                         this.min = min;
477                         this.max = max;
478                         autoAdjustRange();
479                 }
480                 
481                 @Override
482                 protected void autoAdjustRange() {
483                         this.setRange(min, max);
484                 }
485         }
486         
487         
488         /**
489          * Static method that shows a plot with the specified parameters.
490          * 
491          * @param parent                the parent window, which will be blocked.
492          * @param simulation    the simulation to plot.
493          * @param config                the configuration of the plot.
494          */
495         public static void showPlot(Window parent, Simulation simulation, PlotConfiguration config) {
496                 new PlotDialog(parent, simulation, config).setVisible(true);
497         }
498         
499         
500
501         private static Color getEventColor(FlightEvent.Type type) {
502                 Color c = EVENT_COLORS.get(type);
503                 if (c != null)
504                         return c;
505                 return DEFAULT_EVENT_COLOR;
506         }
507         
508         
509
510
511
512         /**
513          * A modification to the standard renderer that renders the domain marker
514          * labels vertically instead of horizontally.
515          */
516         private static class ModifiedXYItemRenderer extends StandardXYItemRenderer {
517                 
518                 @Override
519                 public void drawDomainMarker(Graphics2D g2, XYPlot plot, ValueAxis domainAxis,
520                                 Marker marker, Rectangle2D dataArea) {
521                         
522                         if (!(marker instanceof ValueMarker)) {
523                                 // Use parent for all others
524                                 super.drawDomainMarker(g2, plot, domainAxis, marker, dataArea);
525                                 return;
526                         }
527                         
528                         /*
529                          * Draw the normal marker, but with rotated text.
530                          * Copied from the overridden method.
531                          */
532                         ValueMarker vm = (ValueMarker) marker;
533                         double value = vm.getValue();
534                         Range range = domainAxis.getRange();
535                         if (!range.contains(value)) {
536                                 return;
537                         }
538                         
539                         double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
540                         
541                         PlotOrientation orientation = plot.getOrientation();
542                         Line2D line = null;
543                         if (orientation == PlotOrientation.HORIZONTAL) {
544                                 line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
545                         } else {
546                                 line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
547                         }
548                         
549                         final Composite originalComposite = g2.getComposite();
550                         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, marker
551                                         .getAlpha()));
552                         g2.setPaint(marker.getPaint());
553                         g2.setStroke(marker.getStroke());
554                         g2.draw(line);
555                         
556                         String label = marker.getLabel();
557                         RectangleAnchor anchor = marker.getLabelAnchor();
558                         if (label != null) {
559                                 Font labelFont = marker.getLabelFont();
560                                 g2.setFont(labelFont);
561                                 g2.setPaint(marker.getLabelPaint());
562                                 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
563                                                 orientation, dataArea, line.getBounds2D(), marker
564                                                                 .getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
565                                 
566                                 // Changed:
567                                 TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
568                                 TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
569                                                 (float) coordinates.getY(), textAnchor,
570                                                 -Math.PI / 2, textAnchor);
571                         }
572                         g2.setComposite(originalComposite);
573                 }
574                 
575         }
576         
577 }