package net.sf.openrocket.gui.plot;
import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
+import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.components.StyledLabel;
+import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.FlightDataBranch;
+import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.GUIUtil;
import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Pair;
import net.sf.openrocket.util.Prefs;
import org.jfree.chart.ChartFactory;
public class PlotDialog extends JDialog {
- private static final Color DEFAULT_EVENT_COLOR = new Color(0,0,0);
+ private static final float PLOT_STROKE_WIDTH = 1.5f;
+ private static final Translator trans = Application.getTranslator();
+
+ private static final Color DEFAULT_EVENT_COLOR = new Color(0, 0, 0);
private static final Map<FlightEvent.Type, Color> EVENT_COLORS =
- new HashMap<FlightEvent.Type, Color>();
+ new HashMap<FlightEvent.Type, Color>();
static {
- EVENT_COLORS.put(FlightEvent.Type.LAUNCH, new Color(255,0,0));
- EVENT_COLORS.put(FlightEvent.Type.LIFTOFF, new Color(0,80,196));
- EVENT_COLORS.put(FlightEvent.Type.LAUNCHROD, new Color(0,100,80));
- EVENT_COLORS.put(FlightEvent.Type.IGNITION, new Color(230,130,15));
- EVENT_COLORS.put(FlightEvent.Type.BURNOUT, new Color(80,55,40));
- EVENT_COLORS.put(FlightEvent.Type.EJECTION_CHARGE, new Color(80,55,40));
- EVENT_COLORS.put(FlightEvent.Type.STAGE_SEPARATION, new Color(80,55,40));
- EVENT_COLORS.put(FlightEvent.Type.APOGEE, new Color(15,120,15));
- EVENT_COLORS.put(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, new Color(0,0,128));
- EVENT_COLORS.put(FlightEvent.Type.GROUND_HIT, new Color(0,0,0));
- EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128,0,0));
+ EVENT_COLORS.put(FlightEvent.Type.LAUNCH, new Color(255, 0, 0));
+ EVENT_COLORS.put(FlightEvent.Type.LIFTOFF, new Color(0, 80, 196));
+ EVENT_COLORS.put(FlightEvent.Type.LAUNCHROD, new Color(0, 100, 80));
+ EVENT_COLORS.put(FlightEvent.Type.IGNITION, new Color(230, 130, 15));
+ EVENT_COLORS.put(FlightEvent.Type.BURNOUT, new Color(80, 55, 40));
+ EVENT_COLORS.put(FlightEvent.Type.EJECTION_CHARGE, new Color(80, 55, 40));
+ EVENT_COLORS.put(FlightEvent.Type.STAGE_SEPARATION, new Color(80, 55, 40));
+ EVENT_COLORS.put(FlightEvent.Type.APOGEE, new Color(15, 120, 15));
+ EVENT_COLORS.put(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, new Color(0, 0, 128));
+ EVENT_COLORS.put(FlightEvent.Type.GROUND_HIT, new Color(0, 0, 0));
+ EVENT_COLORS.put(FlightEvent.Type.SIMULATION_END, new Color(128, 0, 0));
}
-
+
private static final Map<FlightEvent.Type, Image> EVENT_IMAGES =
- new HashMap<FlightEvent.Type, Image>();
+ new HashMap<FlightEvent.Type, Image>();
static {
- loadImage(FlightEvent.Type.LAUNCH, "pix/spheres/red-16x16.png");
+ loadImage(FlightEvent.Type.LAUNCH, "pix/eventicons/event-launch.png");
loadImage(FlightEvent.Type.LIFTOFF, "pix/eventicons/event-liftoff.png");
loadImage(FlightEvent.Type.LAUNCHROD, "pix/eventicons/event-launchrod.png");
loadImage(FlightEvent.Type.IGNITION, "pix/eventicons/event-ignition.png");
loadImage(FlightEvent.Type.BURNOUT, "pix/eventicons/event-burnout.png");
- loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/spheres/green-16x16.png");
- loadImage(FlightEvent.Type.STAGE_SEPARATION, "pix/eventicons/event-stage-separation.png");
+ loadImage(FlightEvent.Type.EJECTION_CHARGE, "pix/eventicons/event-ejection-charge.png");
+ loadImage(FlightEvent.Type.STAGE_SEPARATION,
+ "pix/eventicons/event-stage-separation.png");
loadImage(FlightEvent.Type.APOGEE, "pix/eventicons/event-apogee.png");
- loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, "pix/spheres/blue-16x16.png");
- loadImage(FlightEvent.Type.GROUND_HIT, "pix/spheres/gray-16x16.png");
+ loadImage(FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT,
+ "pix/eventicons/event-recovery-device-deployment.png");
+ loadImage(FlightEvent.Type.GROUND_HIT, "pix/eventicons/event-ground-hit.png");
loadImage(FlightEvent.Type.SIMULATION_END, "pix/eventicons/event-simulation-end.png");
}
+
+ private static void loadImage(FlightEvent.Type type, String file) {
+ InputStream is;
+
+ is = ClassLoader.getSystemResourceAsStream(file);
+ if (is == null) {
+ System.out.println("ERROR: File " + file + " not found!");
+ return;
+ }
+
+ try {
+ Image image = ImageIO.read(is);
+ EVENT_IMAGES.put(type, image);
+ } catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+ }
+
+
+
- private static void loadImage(FlightEvent.Type type, String file) {
- InputStream is;
-
- is = ClassLoader.getSystemResourceAsStream(file);
- if (is == null) {
- System.out.println("ERROR: File " + file + " not found!");
- return;
- }
-
- try {
- Image image = ImageIO.read(is);
- EVENT_IMAGES.put(type, image);
- } catch (IOException ignore) {
- ignore.printStackTrace();
- }
- }
-
-
-
-
- private final List<ModifiedXYItemRenderer> renderers =
- new ArrayList<ModifiedXYItemRenderer>();
+ private final List<ModifiedXYItemRenderer> renderers =
+ new ArrayList<ModifiedXYItemRenderer>();
private PlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
- super(parent, "Flight data plot");
+ //// Flight data plot
+ super(parent, trans.get("PlotDialog.title.Flightdataplot"));
this.setModalityType(ModalityType.DOCUMENT_MODAL);
final boolean initialShowPoints = Prefs.getBoolean(Prefs.PLOT_SHOW_POINTS, false);
-
+
// Fill the auto-selections
FlightDataBranch branch = simulation.getSimulatedData().getBranch(0);
PlotConfiguration filled = config.fillAutoAxes(branch);
List<Axis> axes = filled.getAllAxes();
-
+
// Create the data series for both axes
XYSeriesCollection[] data = new XYSeriesCollection[2];
data[0] = new XYSeriesCollection();
data[1] = new XYSeriesCollection();
-
+
// Get the domain axis type
- final FlightDataBranch.Type domainType = filled.getDomainAxisType();
+ final FlightDataType domainType = filled.getDomainAxisType();
final Unit domainUnit = filled.getDomainAxisUnit();
if (domainType == null) {
throw new IllegalArgumentException("Domain axis type not specified.");
}
List<Double> x = branch.get(domainType);
-
+
// Get plot length (ignore trailing NaN's)
int typeCount = filled.getTypeCount();
int dataLength = 0;
- for (int i=0; i<typeCount; i++) {
- FlightDataBranch.Type type = filled.getType(i);
+ for (int i = 0; i < typeCount; i++) {
+ FlightDataType type = filled.getType(i);
List<Double> y = branch.get(type);
for (int j = dataLength; j < y.size(); j++) {
}
dataLength = Math.min(dataLength, x.size());
-
+
// Create the XYSeries objects from the flight data and store into the collections
String[] axisLabel = new String[2];
for (int i = 0; i < typeCount; i++) {
// Get info
- FlightDataBranch.Type type = filled.getType(i);
+ FlightDataType type = filled.getType(i);
Unit unit = filled.getUnit(i);
int axis = filled.getAxis(i);
String name = getLabel(type, unit);
// Store data in provided units
List<Double> y = branch.get(type);
XYSeries series = new XYSeries(name, false, true);
- for (int j=0; j < dataLength; j++) {
+ for (int j = 0; j < dataLength; j++) {
series.add(domainUnit.toUnit(x.get(j)), unit.toUnit(y.get(j)));
}
data[axis].addSeries(series);
-
+
// Update axis label
if (axisLabel[axis] == null)
axisLabel[axis] = type.getName();
axisLabel[axis] += "; " + type.getName();
}
-
+
// Create the chart using the factory to get all default settings
- JFreeChart chart = ChartFactory.createXYLineChart(
- "Simulated flight",
- null,
- null,
- null,
- PlotOrientation.VERTICAL,
- true,
- true,
- false
- );
-
- chart.addSubtitle(new TextTitle(config.getName()));
-
+ JFreeChart chart = ChartFactory.createXYLineChart(
+ //// Simulated flight
+ trans.get("PlotDialog.Chart.Simulatedflight"),
+ null,
+ null,
+ null,
+ PlotOrientation.VERTICAL,
+ true,
+ true,
+ false
+ );
+
+ chart.addSubtitle(new TextTitle(config.getName()));
+
// Add the data and formatting to the plot
XYPlot plot = chart.getXYPlot();
int axisno = 0;
- for (int i=0; i<2; i++) {
+ for (int i = 0; i < 2; i++) {
// Check whether axis has any data
if (data[i].getSeriesCount() > 0) {
// Create and set axis
double max = axes.get(i).getMaxValue();
NumberAxis axis = new PresetNumberAxis(min, max);
axis.setLabel(axisLabel[i]);
-// axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
+ // axis.setRange(axes.get(i).getMinValue(), axes.get(i).getMaxValue());
plot.setRangeAxis(axisno, axis);
// Add data and map to the axis
ModifiedXYItemRenderer r = new ModifiedXYItemRenderer();
r.setBaseShapesVisible(initialShowPoints);
r.setBaseShapesFilled(true);
+ for (int j = 0; j < data[i].getSeriesCount(); j++) {
+ r.setSeriesStroke(j, new BasicStroke(PLOT_STROKE_WIDTH));
+ }
renderers.add(r);
plot.setRenderer(axisno, r);
plot.mapDatasetToRangeAxis(axisno, axisno);
}
}
- plot.getDomainAxis().setLabel(getLabel(domainType,domainUnit));
+ plot.getDomainAxis().setLabel(getLabel(domainType, domainUnit));
plot.addDomainMarker(new ValueMarker(0));
plot.addRangeMarker(new ValueMarker(0));
-
-
+
+
// Create list of events to show (combine event too close to each other)
ArrayList<Double> timeList = new ArrayList<Double>();
ArrayList<String> eventList = new ArrayList<String>();
Color color = null;
Image image = null;
- List<Pair<Double, FlightEvent>> events = branch.getEvents();
- for (int i=0; i < events.size(); i++) {
- Pair<Double, FlightEvent> event = events.get(i);
- double t = event.getU();
- FlightEvent.Type type = event.getV().getType();
+ List<FlightEvent> events = branch.getEvents();
+ for (int i = 0; i < events.size(); i++) {
+ FlightEvent event = events.get(i);
+ double t = event.getTime();
+ FlightEvent.Type type = event.getType();
if (type != FlightEvent.Type.ALTITUDE && config.isEventActive(type)) {
if (Math.abs(t - prevTime) <= 0.01) {
if (!typeSet.contains(type)) {
- text = text + ", " + event.getV().getType().toString();
+ text = text + ", " + type.toString();
color = getEventColor(type);
image = EVENT_IMAGES.get(type);
typeSet.add(type);
imageList.add(image);
}
-
+
// Create the event markers
- if (config.getDomainAxisType() == FlightDataBranch.TYPE_TIME) {
+ if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
// Domain time is plotted as vertical markers
- for (int i=0; i < eventList.size(); i++) {
+ for (int i = 0; i < eventList.size(); i++) {
double t = timeList.get(i);
String event = eventList.get(i);
color = colorList.get(i);
} else {
// Other domains are plotted as image annotations
- List<Double> time = branch.get(FlightDataBranch.TYPE_TIME);
+ List<Double> time = branch.get(FlightDataType.TYPE_TIME);
List<Double> domain = branch.get(config.getDomainAxisType());
- for (int i=0; i < eventList.size(); i++) {
+ for (int i = 0; i < eventList.size(); i++) {
final double t = timeList.get(i);
String event = eventList.get(i);
image = imageList.get(i);
final double a;
int tindex = Collections.binarySearch(time, t);
if (tindex < 0) {
- tindex = -tindex -1;
+ tindex = -tindex - 1;
}
if (tindex >= time.size()) {
// index greater than largest value in time list
- tindex = time.size()-1;
+ tindex = time.size() - 1;
a = 0;
} else if (tindex <= 0) {
// index smaller than smallest value in time list
tindex = 0;
a = 0;
} else {
- assert(tindex > 0);
tindex--;
double t1 = time.get(tindex);
- double t2 = time.get(tindex+1);
+ double t2 = time.get(tindex + 1);
if ((t1 > t) || (t2 < t)) {
- throw new RuntimeException("BUG: t1="+t1+" t2="+t2+" t="+t);
+ throw new BugException("t1=" + t1 + " t2=" + t2 + " t=" + t);
}
if (MathUtil.equals(t1, t2)) {
a = 0;
} else {
- a = 1 - (t-t1) / (t2-t1);
+ a = 1 - (t - t1) / (t2 - t1);
}
}
- final double xcoord;
+ double xcoord;
if (a == 0) {
xcoord = domain.get(tindex);
} else {
- xcoord = a * domain.get(tindex) + (1-a) * domain.get(tindex+1);
+ xcoord = a * domain.get(tindex) + (1 - a) * domain.get(tindex + 1);
}
for (int index = 0; index < config.getTypeCount(); index++) {
- FlightDataBranch.Type type = config.getType(index);
+ FlightDataType type = config.getType(index);
List<Double> range = branch.get(type);
- final double ycoord;
+ // Image annotations are not supported on the right-side axis
+ // TODO: LOW: Can this be achieved by JFreeChart?
+ if (filled.getAxis(index) != SimulationPlotPanel.LEFT) {
+ continue;
+ }
+
+ double ycoord;
if (a == 0) {
ycoord = range.get(tindex);
} else {
- ycoord = a * range.get(tindex) + (1-a) * range.get(tindex+1);
+ ycoord = a * range.get(tindex) + (1 - a) * range.get(tindex + 1);
}
- XYImageAnnotation annotation =
- new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
+ // Convert units
+ xcoord = config.getDomainAxisUnit().toUnit(xcoord);
+ ycoord = config.getUnit(index).toUnit(ycoord);
+
+ XYImageAnnotation annotation =
+ new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
annotation.setToolTipText(event);
plot.addAnnotation(annotation);
}
}
}
-
+
// Create the dialog
JPanel panel = new JPanel(new MigLayout("fill"));
ChartPanel chartPanel = new ChartPanel(chart,
false, // properties
- true, // save
+ true, // save
false, // print
- true, // zoom
+ true, // zoom
true); // tooltips
chartPanel.setMouseWheelEnabled(true);
chartPanel.setEnforceFileExtensions(true);
panel.add(chartPanel, "grow, wrap 20lp");
- final JCheckBox check = new JCheckBox("Show points");
+ //// Show data points
+ final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
check.setSelected(initialShowPoints);
check.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean show = check.isSelected();
Prefs.putBoolean(Prefs.PLOT_SHOW_POINTS, show);
- for (ModifiedXYItemRenderer r: renderers) {
+ for (ModifiedXYItemRenderer r : renderers) {
r.setBaseShapesVisible(show);
}
}
});
panel.add(check, "split, left");
- panel.add(new JPanel(), "growx");
- JButton button = new JButton("Close");
+ JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
+ panel.add(label, "gapleft para");
+
+
+ panel.add(new JPanel(), "growx");
+
+ //// Close button
+ JButton button = new JButton(trans.get("dlg.but.close"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
}
});
panel.add(button, "right");
-
+
this.setLocationByPlatform(true);
this.pack();
- GUIUtil.installEscapeCloseOperation(this);
- GUIUtil.setDefaultButton(button);
+
+ GUIUtil.setDisposableDialogOptions(this, button);
}
-
- private String getLabel(FlightDataBranch.Type type, Unit unit) {
+ private String getLabel(FlightDataType type, Unit unit) {
String name = type.getName();
- if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
+ if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
!UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
- name += " ("+unit.getUnit() + ")";
+ name += " (" + unit.getUnit() + ")";
return name;
}
-
+
private class PresetNumberAxis extends NumberAxis {
private final double min;
private final double max;
}
-
+
private static Color getEventColor(FlightEvent.Type type) {
Color c = EVENT_COLORS.get(type);
if (c != null)
-
-
+
+
/**
* A modification to the standard renderer that renders the domain marker
* labels vertically instead of horizontally.
*/
private static class ModifiedXYItemRenderer extends StandardXYItemRenderer {
-
+
@Override
public void drawDomainMarker(Graphics2D g2, XYPlot plot, ValueAxis domainAxis,
Marker marker, Rectangle2D dataArea) {
-
+
if (!(marker instanceof ValueMarker)) {
// Use parent for all others
super.drawDomainMarker(g2, plot, domainAxis, marker, dataArea);
return;
}
-
+
/*
* Draw the normal marker, but with rotated text.
* Copied from the overridden method.
if (!range.contains(value)) {
return;
}
-
+
double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
-
+
PlotOrientation orientation = plot.getOrientation();
Line2D line = null;
if (orientation == PlotOrientation.HORIZONTAL) {
} else {
line = new Line2D.Double(v, dataArea.getMinY(), v, dataArea.getMaxY());
}
-
+
final Composite originalComposite = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, marker
.getAlpha()));
g2.setPaint(marker.getPaint());
g2.setStroke(marker.getStroke());
g2.draw(line);
-
+
String label = marker.getLabel();
RectangleAnchor anchor = marker.getLabelAnchor();
if (label != null) {
// Changed:
TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
- TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX()+2,
+ TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
(float) coordinates.getY(), textAnchor,
- -Math.PI/2, textAnchor);
+ -Math.PI / 2, textAnchor);
}
g2.setComposite(originalComposite);
}
-
+
}
-
+
}