1 package net.sf.openrocket.gui.main;
5 import java.awt.Component;
6 import java.awt.Window;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.util.Arrays;
10 import java.util.List;
12 import javax.swing.AbstractListModel;
13 import javax.swing.BorderFactory;
14 import javax.swing.JButton;
15 import javax.swing.JCheckBox;
16 import javax.swing.JComboBox;
17 import javax.swing.JDialog;
18 import javax.swing.JLabel;
19 import javax.swing.JList;
20 import javax.swing.JOptionPane;
21 import javax.swing.JPanel;
22 import javax.swing.JScrollPane;
23 import javax.swing.JSpinner;
24 import javax.swing.JTabbedPane;
25 import javax.swing.JTextField;
26 import javax.swing.ListCellRenderer;
27 import javax.swing.event.ChangeEvent;
28 import javax.swing.event.ChangeListener;
29 import javax.swing.event.DocumentEvent;
30 import javax.swing.event.DocumentListener;
32 import net.miginfocom.swing.MigLayout;
33 import net.sf.openrocket.aerodynamics.ExtendedISAModel;
34 import net.sf.openrocket.document.Simulation;
35 import net.sf.openrocket.gui.SpinnerEditor;
36 import net.sf.openrocket.gui.adaptors.BooleanModel;
37 import net.sf.openrocket.gui.adaptors.DoubleModel;
38 import net.sf.openrocket.gui.adaptors.MotorConfigurationModel;
39 import net.sf.openrocket.gui.components.BasicSlider;
40 import net.sf.openrocket.gui.components.DescriptionArea;
41 import net.sf.openrocket.gui.components.SimulationExportPanel;
42 import net.sf.openrocket.gui.components.UnitSelector;
43 import net.sf.openrocket.gui.plot.Axis;
44 import net.sf.openrocket.gui.plot.PlotConfiguration;
45 import net.sf.openrocket.gui.plot.SimulationPlotPanel;
46 import net.sf.openrocket.rocketcomponent.Configuration;
47 import net.sf.openrocket.simulation.FlightData;
48 import net.sf.openrocket.simulation.FlightDataBranch;
49 import net.sf.openrocket.simulation.RK4Simulator;
50 import net.sf.openrocket.simulation.SimulationConditions;
51 import net.sf.openrocket.simulation.SimulationListener;
52 import net.sf.openrocket.simulation.listeners.CSVSaveListener;
53 import net.sf.openrocket.unit.Unit;
54 import net.sf.openrocket.unit.UnitGroup;
55 import net.sf.openrocket.util.Chars;
56 import net.sf.openrocket.util.GUIUtil;
57 import net.sf.openrocket.util.Icons;
58 import net.sf.openrocket.util.Prefs;
60 import org.jfree.chart.ChartFactory;
61 import org.jfree.chart.ChartPanel;
62 import org.jfree.chart.JFreeChart;
63 import org.jfree.chart.axis.NumberAxis;
64 import org.jfree.chart.plot.PlotOrientation;
65 import org.jfree.chart.plot.ValueMarker;
66 import org.jfree.chart.plot.XYPlot;
67 import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
68 import org.jfree.data.xy.XYSeries;
69 import org.jfree.data.xy.XYSeriesCollection;
72 public class SimulationEditDialog extends JDialog {
74 public static final int DEFAULT = -1;
75 public static final int EDIT = 1;
76 public static final int PLOT = 2;
79 private final Window parentWindow;
80 private final Simulation simulation;
81 private final SimulationConditions conditions;
82 private final Configuration configuration;
85 public SimulationEditDialog(Window parent, Simulation s) {
89 public SimulationEditDialog(Window parent, Simulation s, int tab) {
90 super(parent, "Edit simulation", JDialog.ModalityType.DOCUMENT_MODAL);
92 this.parentWindow = parent;
94 this.conditions = simulation.getConditions();
95 configuration = simulation.getConfiguration();
97 JPanel mainPanel = new JPanel(new MigLayout("fill","[grow, fill]"));
99 mainPanel.add(new JLabel("Simulation name: "), "span, split 2, shrink");
100 final JTextField field = new JTextField(simulation.getName());
101 field.getDocument().addDocumentListener(new DocumentListener() {
103 public void changedUpdate(DocumentEvent e) {
107 public void insertUpdate(DocumentEvent e) {
111 public void removeUpdate(DocumentEvent e) {
114 private void setText() {
115 String name = field.getText();
116 if (name == null || name.equals(""))
118 System.out.println("Setting name:"+name);
119 simulation.setName(name);
123 mainPanel.add(field, "shrinky, growx, wrap");
125 JTabbedPane tabbedPane = new JTabbedPane();
128 tabbedPane.addTab("Launch conditions", flightConditionsTab());
129 tabbedPane.addTab("Simulation options", simulationOptionsTab());
130 tabbedPane.addTab("Plot data", plotTab());
131 tabbedPane.addTab("Export data", exportTab());
133 // Select the initial tab
135 tabbedPane.setSelectedIndex(0);
136 } else if (tab == PLOT) {
137 tabbedPane.setSelectedIndex(2);
139 FlightData data = s.getSimulatedData();
140 if (data == null || data.getBranchCount() == 0)
141 tabbedPane.setSelectedIndex(0);
143 tabbedPane.setSelectedIndex(2);
146 mainPanel.add(tabbedPane, "spanx, grow, wrap");
150 mainPanel.add(new JPanel(), "spanx, split, growx");
153 button = new JButton("Run simulation");
154 button.addActionListener(new ActionListener() {
156 public void actionPerformed(ActionEvent e) {
157 SimulationEditDialog.this.dispose();
158 SimulationRunDialog.runSimulations(parentWindow, simulation);
161 mainPanel.add(button, "gapright para");
164 JButton close = new JButton("Close");
165 close.addActionListener(new ActionListener() {
167 public void actionPerformed(ActionEvent e) {
168 SimulationEditDialog.this.dispose();
171 mainPanel.add(close, "");
177 this.setLocationByPlatform(true);
179 GUIUtil.setDisposableDialogOptions(this, button);
186 private JPanel flightConditionsTab() {
187 JPanel panel = new JPanel(new MigLayout("fill"));
196 JLabel label = new JLabel("Motor configuration:");
197 label.setToolTipText("Select the motor configuration to use.");
198 panel.add(label, "shrinkx, spanx, split 2");
200 JComboBox combo = new JComboBox(new MotorConfigurationModel(configuration));
201 combo.setToolTipText("Select the motor configuration to use.");
202 combo.addActionListener(new ActionListener() {
204 public void actionPerformed(ActionEvent e) {
205 conditions.setMotorConfigurationID(configuration.getMotorConfigurationID());
208 panel.add(combo, "growx, wrap para");
211 //// Wind settings: Average wind speed, turbulence intensity, std. deviation
212 sub = new JPanel(new MigLayout("fill, gap rel unrel",
213 "[grow][65lp!][30lp!][75lp!]",""));
214 sub.setBorder(BorderFactory.createTitledBorder("Wind"));
215 panel.add(sub, "growx, split 2, aligny 0, flowy, gapright para");
219 label = new JLabel("Average windspeed:");
220 tip = "The average windspeed relative to the ground.";
221 label.setToolTipText(tip);
224 m = new DoubleModel(conditions,"WindSpeedAverage", UnitGroup.UNITS_VELOCITY,0);
226 spin = new JSpinner(m.getSpinnerModel());
227 spin.setEditor(new SpinnerEditor(spin));
228 spin.setToolTipText(tip);
229 sub.add(spin,"w 65lp!");
231 unit = new UnitSelector(m);
232 unit.setToolTipText(tip);
233 sub.add(unit,"growx");
234 slider = new BasicSlider(m.getSliderModel(0, 10.0));
235 slider.setToolTipText(tip);
236 sub.add(slider,"w 75lp, wrap");
240 // Wind std. deviation
241 label = new JLabel("Standard deviation:");
242 tip = "<html>The standard deviation of the windspeed.<br>" +
243 "The windspeed is within twice the standard deviation from the average for " +
245 label.setToolTipText(tip);
248 m = new DoubleModel(conditions,"WindSpeedDeviation", UnitGroup.UNITS_VELOCITY,0);
249 DoubleModel m2 = new DoubleModel(conditions,"WindSpeedAverage", 0.25,
250 UnitGroup.UNITS_COEFFICIENT,0);
252 spin = new JSpinner(m.getSpinnerModel());
253 spin.setEditor(new SpinnerEditor(spin));
254 spin.setToolTipText(tip);
255 sub.add(spin,"w 65lp!");
257 unit = new UnitSelector(m);
258 unit.setToolTipText(tip);
259 sub.add(unit,"growx");
260 slider = new BasicSlider(m.getSliderModel(new DoubleModel(0), m2));
261 slider.setToolTipText(tip);
262 sub.add(slider,"w 75lp, wrap");
265 // Wind turbulence intensity
266 label = new JLabel("Turbulence intensity:");
267 tip = "<html>The turbulence intensity is the standard deviation " +
268 "divided by the average windspeed.<br>" +
269 "Typical values range from "+
270 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.05) +
272 UnitGroup.UNITS_RELATIVE.getDefaultUnit().toStringUnit(0.20) + ".";
273 label.setToolTipText(tip);
276 m = new DoubleModel(conditions,"WindTurbulenceIntensity", UnitGroup.UNITS_RELATIVE,0);
278 spin = new JSpinner(m.getSpinnerModel());
279 spin.setEditor(new SpinnerEditor(spin));
280 spin.setToolTipText(tip);
281 sub.add(spin,"w 65lp!");
283 unit = new UnitSelector(m);
284 unit.setToolTipText(tip);
285 sub.add(unit,"growx");
287 final JLabel intensityLabel = new JLabel(
288 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
289 intensityLabel.setToolTipText(tip);
290 sub.add(intensityLabel,"w 75lp, wrap");
291 m.addChangeListener(new ChangeListener() {
293 public void stateChanged(ChangeEvent e) {
294 intensityLabel.setText(
295 getIntensityDescription(conditions.getWindTurbulenceIntensity()));
303 //// Temperature and pressure
304 sub = new JPanel(new MigLayout("fill, gap rel unrel",
305 "[grow][65lp!][30lp!][75lp!]",""));
306 sub.setBorder(BorderFactory.createTitledBorder("Atmospheric conditions"));
307 panel.add(sub, "growx, aligny 0, gapright para");
310 BooleanModel isa = new BooleanModel(conditions, "ISAAtmosphere");
311 JCheckBox check = new JCheckBox(isa);
312 check.setText("Use International Standard Atmosphere");
313 check.setToolTipText("<html>Select to use the International Standard Atmosphere model."+
314 "<br>This model has a temperature of " +
315 UnitGroup.UNITS_TEMPERATURE.toStringUnit(ExtendedISAModel.STANDARD_TEMPERATURE)+
316 " and a pressure of " +
317 UnitGroup.UNITS_PRESSURE.toStringUnit(ExtendedISAModel.STANDARD_PRESSURE) +
319 sub.add(check, "spanx, wrap unrel");
322 label = new JLabel("Temperature:");
323 tip = "The temperature at the launch site.";
324 label.setToolTipText(tip);
325 isa.addEnableComponent(label, false);
328 m = new DoubleModel(conditions,"LaunchTemperature", UnitGroup.UNITS_TEMPERATURE,0);
330 spin = new JSpinner(m.getSpinnerModel());
331 spin.setEditor(new SpinnerEditor(spin));
332 spin.setToolTipText(tip);
333 isa.addEnableComponent(spin, false);
334 sub.add(spin,"w 65lp!");
336 unit = new UnitSelector(m);
337 unit.setToolTipText(tip);
338 isa.addEnableComponent(unit, false);
339 sub.add(unit,"growx");
340 slider = new BasicSlider(m.getSliderModel(253.15, 308.15)); // -20 ... 35
341 slider.setToolTipText(tip);
342 isa.addEnableComponent(slider, false);
343 sub.add(slider,"w 75lp, wrap");
348 label = new JLabel("Pressure:");
349 tip = "The atmospheric pressure at the launch site.";
350 label.setToolTipText(tip);
351 isa.addEnableComponent(label, false);
354 m = new DoubleModel(conditions,"LaunchPressure", UnitGroup.UNITS_PRESSURE,0);
356 spin = new JSpinner(m.getSpinnerModel());
357 spin.setEditor(new SpinnerEditor(spin));
358 spin.setToolTipText(tip);
359 isa.addEnableComponent(spin, false);
360 sub.add(spin,"w 65lp!");
362 unit = new UnitSelector(m);
363 unit.setToolTipText(tip);
364 isa.addEnableComponent(unit, false);
365 sub.add(unit,"growx");
366 slider = new BasicSlider(m.getSliderModel(0.950e5, 1.050e5));
367 slider.setToolTipText(tip);
368 isa.addEnableComponent(slider, false);
369 sub.add(slider,"w 75lp, wrap");
375 //// Launch site conditions
376 sub = new JPanel(new MigLayout("fill, gap rel unrel",
377 "[grow][65lp!][30lp!][75lp!]",""));
378 sub.setBorder(BorderFactory.createTitledBorder("Launch site"));
379 panel.add(sub, "growx, split 2, aligny 0, flowy");
383 label = new JLabel("Latitude:");
384 tip = "<html>The launch site latitude affects the gravitational pull of Earth.<br>" +
385 "Positive values are on the Northern hemisphere, negative values on the " +
386 "Southern hemisphere.";
387 label.setToolTipText(tip);
390 m = new DoubleModel(conditions,"LaunchLatitude", UnitGroup.UNITS_NONE, -90, 90);
392 spin = new JSpinner(m.getSpinnerModel());
393 spin.setEditor(new SpinnerEditor(spin));
394 spin.setToolTipText(tip);
395 sub.add(spin,"w 65lp!");
397 label = new JLabel(Chars.DEGREE + " N");
398 label.setToolTipText(tip);
399 sub.add(label,"growx");
400 slider = new BasicSlider(m.getSliderModel(-90, 90));
401 slider.setToolTipText(tip);
402 sub.add(slider,"w 75lp, wrap");
407 label = new JLabel("Altitude:");
408 tip = "<html>The launch altitude above mean sea level.<br>" +
409 "This affects the position of the rocket in the atmospheric model.";
410 label.setToolTipText(tip);
413 m = new DoubleModel(conditions,"LaunchAltitude", UnitGroup.UNITS_DISTANCE,0);
415 spin = new JSpinner(m.getSpinnerModel());
416 spin.setEditor(new SpinnerEditor(spin));
417 spin.setToolTipText(tip);
418 sub.add(spin,"w 65lp!");
420 unit = new UnitSelector(m);
421 unit.setToolTipText(tip);
422 sub.add(unit,"growx");
423 slider = new BasicSlider(m.getSliderModel(0, 250, 1000));
424 slider.setToolTipText(tip);
425 sub.add(slider,"w 75lp, wrap");
432 sub = new JPanel(new MigLayout("fill, gap rel unrel",
433 "[grow][65lp!][30lp!][75lp!]",""));
434 sub.setBorder(BorderFactory.createTitledBorder("Launch rod"));
435 panel.add(sub, "growx, aligny 0, wrap");
439 label = new JLabel("Length:");
440 tip = "The length of the launch rod.";
441 label.setToolTipText(tip);
444 m = new DoubleModel(conditions,"LaunchRodLength", UnitGroup.UNITS_LENGTH, 0);
446 spin = new JSpinner(m.getSpinnerModel());
447 spin.setEditor(new SpinnerEditor(spin));
448 spin.setToolTipText(tip);
449 sub.add(spin,"w 65lp!");
451 unit = new UnitSelector(m);
452 unit.setToolTipText(tip);
453 sub.add(unit,"growx");
454 slider = new BasicSlider(m.getSliderModel(0, 1, 5));
455 slider.setToolTipText(tip);
456 sub.add(slider,"w 75lp, wrap");
461 label = new JLabel("Angle:");
462 tip = "The angle of the launch rod from vertical.";
463 label.setToolTipText(tip);
466 m = new DoubleModel(conditions,"LaunchRodAngle", UnitGroup.UNITS_ANGLE,
467 0, SimulationConditions.MAX_LAUNCH_ROD_ANGLE);
469 spin = new JSpinner(m.getSpinnerModel());
470 spin.setEditor(new SpinnerEditor(spin));
471 spin.setToolTipText(tip);
472 sub.add(spin,"w 65lp!");
474 unit = new UnitSelector(m);
475 unit.setToolTipText(tip);
476 sub.add(unit,"growx");
477 slider = new BasicSlider(m.getSliderModel(0, Math.PI/9,
478 SimulationConditions.MAX_LAUNCH_ROD_ANGLE));
479 slider.setToolTipText(tip);
480 sub.add(slider,"w 75lp, wrap");
485 label = new JLabel("Direction:");
486 tip = "<html>Direction of the launch rod relative to the wind.<br>" +
487 UnitGroup.UNITS_ANGLE.toStringUnit(0) +
488 " = towards the wind, "+
489 UnitGroup.UNITS_ANGLE.toStringUnit(Math.PI) +
491 label.setToolTipText(tip);
494 m = new DoubleModel(conditions,"LaunchRodDirection", UnitGroup.UNITS_ANGLE,
497 spin = new JSpinner(m.getSpinnerModel());
498 spin.setEditor(new SpinnerEditor(spin));
499 spin.setToolTipText(tip);
500 sub.add(spin,"w 65lp!");
502 unit = new UnitSelector(m);
503 unit.setToolTipText(tip);
504 sub.add(unit,"growx");
505 slider = new BasicSlider(m.getSliderModel(-Math.PI, Math.PI));
506 slider.setToolTipText(tip);
507 sub.add(slider,"w 75lp, wrap");
513 private String getIntensityDescription(double i) {
531 private JPanel simulationOptionsTab() {
532 JPanel panel = new JPanel(new MigLayout("fill"));
542 //// Simulation options
543 sub = new JPanel(new MigLayout("fill, gap rel unrel",
544 "[grow][65lp!][30lp!][75lp!]",""));
545 sub.setBorder(BorderFactory.createTitledBorder("Simulator options"));
546 panel.add(sub, "w 330lp!, growy, aligny 0");
549 // Calculation method
551 "The Extended Barrowman method calculates aerodynamic forces according <br>" +
552 "to the Barrowman equations extended to accommodate more components.";
554 label = new JLabel("Calculation method:");
555 label.setToolTipText(tip);
556 sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
558 label = new JLabel("Extended Barrowman");
559 label.setToolTipText(tip);
560 sub.add(label, "growx, wrap para");
565 "The six degree-of-freedom simulator allows the rocket total freedom during " +
567 "Integration is performed using a 4<sup>th</sup> order Runge-Kutta 4 " +
568 "numerical integration.";
570 label = new JLabel("Simulation method:");
571 label.setToolTipText(tip);
572 sub.add(label, "gaptop unrel, gapright para, spanx, split 2, w 150lp!");
574 label = new JLabel("6-DOF Runge-Kutta 4");
575 label.setToolTipText(tip);
576 sub.add(label, "growx, wrap 35lp");
580 label = new JLabel("Time step:");
581 tip = "<html>The time between simulation steps.<br>" +
582 "A smaller time step results in a more accurate but slower simulation.<br>" +
583 "The 4<sup>th</sup> order simulation method is quite accurate with a time " +
585 UnitGroup.UNITS_TIME_STEP.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
587 label.setToolTipText(tip);
590 m = new DoubleModel(conditions,"TimeStep", UnitGroup.UNITS_TIME_STEP, 0, 1);
592 spin = new JSpinner(m.getSpinnerModel());
593 spin.setEditor(new SpinnerEditor(spin));
594 spin.setToolTipText(tip);
595 sub.add(spin,"w 65lp!");
597 unit = new UnitSelector(m);
598 unit.setToolTipText(tip);
599 sub.add(unit,"growx");
600 slider = new BasicSlider(m.getSliderModel(0, 0.2));
601 slider.setToolTipText(tip);
602 sub.add(slider,"w 75lp, wrap");
606 // Maximum angle step
608 label = new JLabel("Max. angle step:");
610 "This defines the maximum angle the rocket will turn during one time step.<br>"+
611 "Smaller values result in a more accurate but possibly slower simulation.<br>"+
612 "A recommended value is " +
613 UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".";
614 label.setToolTipText(tip);
617 m = new DoubleModel(conditions,"MaximumStepAngle", UnitGroup.UNITS_ANGLE,
618 1*Math.PI/180, Math.PI/9);
620 spin = new JSpinner(m.getSpinnerModel());
621 spin.setEditor(new SpinnerEditor(spin));
622 spin.setToolTipText(tip);
623 sub.add(spin,"w 65lp!");
625 unit = new UnitSelector(m);
626 unit.setToolTipText(tip);
627 sub.add(unit,"growx");
628 slider = new BasicSlider(m.getSliderModel(0, Math.toRadians(10)));
629 slider.setToolTipText(tip);
630 sub.add(slider,"w 75lp, wrap para");
633 JButton button = new JButton("Reset to default");
634 button.setToolTipText("Reset the time step to its default value (" +
635 UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
637 button.addActionListener(new ActionListener() {
639 public void actionPerformed(ActionEvent e) {
640 conditions.setTimeStep(RK4Simulator.RECOMMENDED_TIME_STEP);
644 // button.setToolTipText("<html>Reset the step value to its default:<br>" +
646 // UnitGroup.UNITS_SHORT_TIME.toStringUnit(RK4Simulator.RECOMMENDED_TIME_STEP) +
647 // "; maximum angle step " +
648 // UnitGroup.UNITS_ANGLE.toStringUnit(RK4Simulator.RECOMMENDED_ANGLE_STEP) + ".");
649 sub.add(button, "spanx, tag right, wrap para");
654 //// Simulation listeners
655 sub = new JPanel(new MigLayout("fill, gap 0 0"));
656 sub.setBorder(BorderFactory.createTitledBorder("Simulator listeners"));
657 panel.add(sub, "growx, growy");
660 DescriptionArea desc = new DescriptionArea(5);
661 desc.setText("<html>" +
662 "<i>Simulation listeners</i> is an advanced feature that allows "+
663 "user-written code to listen to and interact with the simulation. " +
664 "For details on writing simulation listeners, see the OpenRocket " +
665 "technical documentation.");
666 sub.add(desc, "aligny 0, growx, wrap para");
669 label = new JLabel("Current listeners:");
670 sub.add(label, "spanx, wrap rel");
672 final ListenerListModel listenerModel = new ListenerListModel();
673 final JList list = new JList(listenerModel);
674 list.setCellRenderer(new ListenerCellRenderer());
675 JScrollPane scroll = new JScrollPane(list);
676 // scroll.setPreferredSize(new Dimension(1,1));
677 sub.add(scroll, "height 1px, grow, wrap rel");
680 button = new JButton("Add");
681 button.addActionListener(new ActionListener() {
683 public void actionPerformed(ActionEvent e) {
684 String previous = Prefs.NODE.get("previousListenerName", "");
685 String input = (String)JOptionPane.showInputDialog(SimulationEditDialog.this,
687 "Type the full Java class name of the simulation listener, for example:",
688 "<html><tt>" + CSVSaveListener.class.getName() + "</tt>" },
689 "Add simulation listener",
690 JOptionPane.QUESTION_MESSAGE,
694 if (input == null || input.equals(""))
697 Prefs.NODE.put("previousListenerName", input);
698 simulation.getSimulationListeners().add(input);
699 listenerModel.fireContentsChanged();
702 sub.add(button, "split 2, sizegroup buttons, alignx 50%, gapright para");
704 button = new JButton("Remove");
705 button.addActionListener(new ActionListener() {
707 public void actionPerformed(ActionEvent e) {
708 int[] selected = list.getSelectedIndices();
709 Arrays.sort(selected);
710 for (int i=selected.length-1; i>=0; i--) {
711 simulation.getSimulationListeners().remove(selected[i]);
713 listenerModel.fireContentsChanged();
716 sub.add(button, "sizegroup buttons, alignx 50%");
723 private class ListenerListModel extends AbstractListModel {
725 public String getElementAt(int index) {
726 if (index < 0 || index >= getSize())
728 return simulation.getSimulationListeners().get(index);
731 public int getSize() {
732 return simulation.getSimulationListeners().size();
734 public void fireContentsChanged() {
735 super.fireContentsChanged(this, 0, getSize());
743 * A panel for plotting the previously calculated data.
745 private JPanel plotTab() {
747 // Check that data exists
748 if (simulation.getSimulatedData() == null ||
749 simulation.getSimulatedData().getBranchCount() == 0) {
750 return noDataPanel();
753 return new SimulationPlotPanel(simulation);
759 * A panel for exporting the data.
761 private JPanel exportTab() {
762 FlightData data = simulation.getSimulatedData();
764 // Check that data exists
765 if (data == null || data.getBranchCount() == 0 ||
766 data.getBranch(0).getTypes().length == 0) {
767 return noDataPanel();
770 return new SimulationExportPanel(simulation);
778 * Return a panel stating that there is no data available, and that the user
779 * should run the simulation first.
781 public static JPanel noDataPanel() {
782 JPanel panel = new JPanel(new MigLayout("fill"));
785 panel.add(new JLabel("No flight data available."),
786 "alignx 50%, aligny 100%, wrap para");
787 panel.add(new JLabel("Please run the simulation first."),
788 "alignx 50%, aligny 0%, wrap");
793 private void performPlot(PlotConfiguration config) {
795 // Fill the auto-selections
796 FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
797 PlotConfiguration filled = config.fillAutoAxes(branch);
798 List<Axis> axes = filled.getAllAxes();
801 // Create the data series for both axes
802 XYSeriesCollection[] data = new XYSeriesCollection[2];
803 data[0] = new XYSeriesCollection();
804 data[1] = new XYSeriesCollection();
807 // Get the domain axis type
808 final FlightDataBranch.Type domainType = filled.getDomainAxisType();
809 final Unit domainUnit = filled.getDomainAxisUnit();
810 if (domainType == null) {
811 throw new IllegalArgumentException("Domain axis type not specified.");
813 List<Double> x = branch.get(domainType);
816 // Create the XYSeries objects from the flight data and store into the collections
817 int length = filled.getTypeCount();
818 String[] axisLabel = new String[2];
819 for (int i = 0; i < length; i++) {
821 FlightDataBranch.Type type = filled.getType(i);
822 Unit unit = filled.getUnit(i);
823 int axis = filled.getAxis(i);
824 String name = getLabel(type, unit);
826 // Store data in provided units
827 List<Double> y = branch.get(type);
828 XYSeries series = new XYSeries(name, false, true);
829 for (int j=0; j<x.size(); j++) {
830 series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
832 data[axis].addSeries(series);
835 if (axisLabel[axis] == null)
836 axisLabel[axis] = type.getName();
838 axisLabel[axis] += "; " + type.getName();
842 // Create the chart using the factory to get all default settings
843 JFreeChart chart = ChartFactory.createXYLineChart(
848 PlotOrientation.VERTICAL,
855 // Add the data and formatting to the plot
856 XYPlot plot = chart.getXYPlot();
858 for (int i=0; i<2; i++) {
859 // Check whether axis has any data
860 if (data[i].getSeriesCount() > 0) {
861 // Create and set axis
862 double min = axes.get(i).getMinValue();
863 double max = axes.get(i).getMaxValue();
864 NumberAxis axis = new PresetNumberAxis(min, max);
865 axis.setLabel(axisLabel[i]);
866 // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
867 plot.setRangeAxis(axisno, axis);
869 // Add data and map to the axis
870 plot.setDataset(axisno, data[i]);
871 plot.setRenderer(axisno, new StandardXYItemRenderer());
872 plot.mapDatasetToRangeAxis(axisno, axisno);
877 plot.getDomainAxis().setLabel(getLabel(domainType,domainUnit));
878 plot.addDomainMarker(new ValueMarker(0));
879 plot.addRangeMarker(new ValueMarker(0));
883 final JDialog dialog = new JDialog(this, "Simulation results");
884 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
886 JPanel panel = new JPanel(new MigLayout("fill"));
889 ChartPanel chartPanel = new ChartPanel(chart,
895 chartPanel.setMouseWheelEnabled(true);
896 chartPanel.setEnforceFileExtensions(true);
897 chartPanel.setInitialDelay(500);
899 chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
901 panel.add(chartPanel, "grow, wrap 20lp");
903 JButton button = new JButton("Close");
904 button.addActionListener(new ActionListener() {
906 public void actionPerformed(ActionEvent e) {
907 dialog.setVisible(false);
910 panel.add(button, "right");
912 dialog.setLocationByPlatform(true);
915 GUIUtil.setDisposableDialogOptions(dialog, button);
917 dialog.setVisible(true);
921 private class PresetNumberAxis extends NumberAxis {
922 private final double min;
923 private final double max;
925 public PresetNumberAxis(double min, double max) {
932 protected void autoAdjustRange() {
933 this.setRange(min, max);
938 private String getLabel(FlightDataBranch.Type type, Unit unit) {
939 String name = type.getName();
940 if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
941 !UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
942 name += " ("+unit.getUnit() + ")";
948 private class ListenerCellRenderer extends JLabel implements ListCellRenderer {
950 public Component getListCellRendererComponent(JList list, Object value,
951 int index, boolean isSelected, boolean cellHasFocus) {
952 String s = value.toString();
955 // Attempt instantiating, catch any exceptions
958 Class<?> c = Class.forName(s);
959 @SuppressWarnings("unused")
960 SimulationListener l = (SimulationListener)c.newInstance();
961 } catch (Exception e) {
966 setIcon(Icons.SIMULATION_LISTENER_OK);
967 setToolTipText("Listener instantiated successfully.");
969 setIcon(Icons.SIMULATION_LISTENER_ERROR);
970 setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
975 setBackground(list.getSelectionBackground());
976 setForeground(list.getSelectionForeground());
978 setBackground(list.getBackground());
979 setForeground(list.getForeground());