Refactored lots of code out of SimulationPlotDialog into SimulationPlot

and EventGraphics to allow for easier maintenance.  Added stage
selection combo box to SimulationPlotDialog.  Removed the selectedStages
from PlotConfiguration since we are going to control which stages to
plot from the plot dialog.
This commit is contained in:
kruland2607 2012-11-08 10:38:48 -06:00
parent 4de4d0d728
commit b98802ebee
6 changed files with 599 additions and 577 deletions

View File

@ -0,0 +1,78 @@
package net.sf.openrocket.gui.plot;
import java.awt.Color;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import net.sf.openrocket.simulation.FlightEvent;
public class EventGraphics {
static Color getEventColor(FlightEvent.Type type) {
Color c = EVENT_COLORS.get(type);
if (c != null)
return c;
return DEFAULT_EVENT_COLOR;
}
static Image getEventImage(FlightEvent.Type type ) {
Image i = EVENT_IMAGES.get(type);
return i;
}
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>();
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));
}
private static final Map<FlightEvent.Type, Image> EVENT_IMAGES = new HashMap<FlightEvent.Type, Image>();
static {
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/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/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();
}
}
}

View File

@ -169,8 +169,6 @@ public class PlotConfiguration implements Cloneable {
private final int axesCount;
private ArrayList<Axis> allAxes = new ArrayList<Axis>();
private List<Integer> selectedBranches = Collections.singletonList(Integer.valueOf(0));
private String name = null;
@ -192,16 +190,6 @@ public class PlotConfiguration implements Cloneable {
setDomainAxisType(domainType);
}
//// Branches to Plot
public void setSelectedBranches( List<Integer> selectedBranches ) {
this.selectedBranches = selectedBranches;
}
public List<Integer> getSelectedBranches() {
// FIXME - unmodifiable?
return this.selectedBranches;
}
//// Axis
public FlightDataType getDomainAxisType() {
@ -741,8 +729,6 @@ public class PlotConfiguration implements Cloneable {
copy.plotDataAxes = this.plotDataAxes.clone();
copy.plotDataUnits = this.plotDataUnits.clone();
copy.events = this.events.clone();
copy.selectedBranches = new ArrayList<Integer>();
copy.selectedBranches.addAll(this.selectedBranches);
// Deep-clone all Axis since they are mutable
copy.allAxes = new ArrayList<Axis>();

View File

@ -1,5 +1,6 @@
package net.sf.openrocket.gui.plot;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
@ -8,6 +9,8 @@ import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import javax.swing.BorderFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
@ -20,6 +23,16 @@ import org.jfree.chart.plot.Zoomable;
import com.jogamp.newt.event.InputEvent;
/**
* Custom implementation of JFreeChart's ChartPanel which changes the mouse handling.
*
* Changed mouse drag (left click + move) to pan the image.
*
* Changed mouse wheel handling. wheel zooms. Alt+wheel zooms only domain axis.
*
* @author kruland
*
*/
public class SimulationChart extends ChartPanel {
private Point2D panLast;
@ -28,36 +41,17 @@ public class SimulationChart extends ChartPanel {
private MouseWheelHandler mouseWheelHandler = null;
public SimulationChart(JFreeChart chart, boolean properties, boolean save,
boolean print, boolean zoom, boolean tooltips) {
super(chart, properties, save, print, zoom, tooltips);
}
public SimulationChart(JFreeChart chart, boolean useBuffer) {
super(chart, useBuffer);
}
public SimulationChart(JFreeChart chart, int width, int height,
int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth,
int maximumDrawHeight, boolean useBuffer, boolean properties,
boolean copy, boolean save, boolean print, boolean zoom,
boolean tooltips) {
super(chart, width, height, minimumDrawWidth, minimumDrawHeight,
maximumDrawWidth, maximumDrawHeight, useBuffer, properties, copy, save,
print, zoom, tooltips);
}
public SimulationChart(JFreeChart chart, int width, int height,
int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth,
int maximumDrawHeight, boolean useBuffer, boolean properties,
boolean save, boolean print, boolean zoom, boolean tooltips) {
super(chart, width, height, minimumDrawWidth, minimumDrawHeight,
maximumDrawWidth, maximumDrawHeight, useBuffer, properties, save,
print, zoom, tooltips);
}
public SimulationChart(JFreeChart chart) {
super(chart);
super(chart,
/* properties */false,
/* save */ true,
/* print */ false,
/* zoom */ true,
/* tooltips */ true);
this.setMouseWheelEnabled(true);
this.setEnforceFileExtensions(true);
this.setInitialDelay(500);
this.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
}
@Override
@ -190,9 +184,8 @@ public class SimulationChart extends ChartPanel {
}
Plot plot = chart.getPlot();
if (plot instanceof Zoomable) {
boolean domainOnly = ( e.getModifiers() & InputEvent.ALT_MASK ) != 0;
Zoomable zoomable = (Zoomable) plot;
handleZoomable(zoomable, e, domainOnly);
handleZoomable(zoomable, e);
}
else if (plot instanceof PiePlot) {
PiePlot pp = (PiePlot) plot;
@ -206,7 +199,7 @@ public class SimulationChart extends ChartPanel {
* @param zoomable the zoomable plot.
* @param e the mouse wheel event.
*/
private void handleZoomable(Zoomable zoomable, MouseWheelEvent e, boolean domainOnly) {
private void handleZoomable(Zoomable zoomable, MouseWheelEvent e) {
// don't zoom unless the mouse pointer is in the plot's data area
ChartRenderingInfo info = SimulationChart.this.getChartRenderingInfo();
PlotRenderingInfo pinfo = info.getPlotInfo();
@ -227,6 +220,7 @@ public class SimulationChart extends ChartPanel {
if (SimulationChart.this.isDomainZoomable()) {
zoomable.zoomDomainAxes(zf, pinfo, p, true);
}
boolean domainOnly = ( e.getModifiers() & InputEvent.ALT_MASK ) != 0;
if (SimulationChart.this.isRangeZoomable() && !domainOnly ) {
zoomable.zoomRangeAxes(zf, pinfo, p, true);
}

View File

@ -0,0 +1,468 @@
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 java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.LinearInterpolator;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYImageAnnotation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.DefaultDrawingSupplier;
import org.jfree.chart.plot.Marker;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.Range;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.text.TextUtilities;
import org.jfree.ui.LengthAdjustmentType;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.TextAnchor;
public class SimulationPlot {
private static final float PLOT_STROKE_WIDTH = 1.5f;
private JFreeChart chart;
private final List<ModifiedXYItemRenderer> renderers = new ArrayList<ModifiedXYItemRenderer>();
int branchCount;
void setShowPoints( boolean showPoints ) {
for (ModifiedXYItemRenderer r : renderers) {
r.setBaseShapesVisible(showPoints);
}
}
void setShowBranch( int branch ) {
XYPlot plot = (XYPlot) chart.getPlot();
int datasetcount = plot.getDatasetCount();
for( int i =0; i< datasetcount; i++ ) {
int seriescount = ((XYSeriesCollection)plot.getDataset(i)).getSeriesCount();
XYItemRenderer r = ((XYPlot)chart.getPlot()).getRenderer(i);
for( int j=0; j<seriescount; j++) {
boolean show = (branch<0) || (j % branchCount == branch);
r.setSeriesVisible(j, show);
}
}
}
SimulationPlot( Simulation simulation, PlotConfiguration config, boolean initialShowPoints ) {
this.chart = ChartFactory.createXYLineChart(
//// Simulated flight
simulation.getName(),
null,
null,
null,
PlotOrientation.VERTICAL,
true,
true,
false
);
chart.addSubtitle(new TextTitle(config.getName()));
branchCount = simulation.getSimulatedData().getBranchCount();
// Fill the auto-selections based on first branch selected.
FlightDataBranch mainBranch = simulation.getSimulatedData().getBranch( 0 );
PlotConfiguration filled = config.fillAutoAxes(mainBranch);
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 FlightDataType domainType = filled.getDomainAxisType();
final Unit domainUnit = filled.getDomainAxisUnit();
if (domainType == null) {
throw new IllegalArgumentException("Domain axis type not specified.");
}
// Get plot length (ignore trailing NaN's)
int typeCount = filled.getTypeCount();
// 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
FlightDataType type = filled.getType(i);
Unit unit = filled.getUnit(i);
int axis = filled.getAxis(i);
String name = getLabel(type, unit);
for( int branchIndex=0; branchIndex<branchCount; branchIndex++ ) {
FlightDataBranch thisBranch = simulation.getSimulatedData().getBranch(branchIndex);
// Store data in provided units
List<Double> plotx = thisBranch.get(domainType);
List<Double> ploty = thisBranch.get(type);
XYSeries series = new XYSeries(thisBranch.getBranchName() + " (" + branchIndex+"): " + name, false, true);
series.setDescription(thisBranch.getBranchName()+": " + name);
int pointCount = plotx.size();
for (int j = 0; j < pointCount; j++) {
series.add(domainUnit.toUnit(plotx.get(j)), unit.toUnit(ploty.get(j)));
}
data[axis].addSeries(series);
}
// Update axis label
if (axisLabel[axis] == null)
axisLabel[axis] = type.getName();
else
axisLabel[axis] += "; " + type.getName();
}
// Add the data and formatting to the plot
XYPlot plot = chart.getXYPlot();
plot.setDomainPannable(true);
plot.setRangePannable(true);
int axisno = 0;
for (int i = 0; i < 2; i++) {
// Check whether axis has any data
if (data[i].getSeriesCount() > 0) {
// Create and set axis
double min = axes.get(i).getMinValue();
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());
plot.setRangeAxis(axisno, axis);
// Add data and map to the axis
plot.setDataset(axisno, data[i]);
ModifiedXYItemRenderer r = new ModifiedXYItemRenderer(branchCount);
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);
axisno++;
}
}
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)
List<EventDisplayInfo> eventList = buildEventInfo(simulation, config);
// Create the event markers
if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
// Domain time is plotted as vertical markers
for ( EventDisplayInfo info : eventList ) {
double t = info.time;
String event = info.event;
Color color = info.color;
ValueMarker m = new ValueMarker(t);
m.setLabel(event);
m.setPaint(color);
m.setLabelPaint(color);
m.setAlpha(0.7f);
plot.addDomainMarker(m);
}
} else {
// Other domains are plotted as image annotations
List<Double> time = mainBranch.get(FlightDataType.TYPE_TIME);
List<Double> domain = mainBranch.get(config.getDomainAxisType());
LinearInterpolator domainInterpolator = new LinearInterpolator( time, domain );
for (EventDisplayInfo info : eventList ) {
final double t = info.time;
String event = info.event;
Image image = info.image;
if (image == null)
continue;
double xcoord = domainInterpolator.getValue(t);
for (int index = 0; index < config.getTypeCount(); index++) {
FlightDataType type = config.getType(index);
List<Double> range = mainBranch.get(type);
LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range);
// 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 = rangeInterpolator.getValue(t);
// 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);
}
}
}
}
JFreeChart getJFreeChart() {
return chart;
}
private String getLabel(FlightDataType type, Unit unit) {
String name = type.getName();
if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
!UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
name += " (" + unit.getUnit() + ")";
return name;
}
private List<EventDisplayInfo> buildEventInfo(Simulation simulation, PlotConfiguration config) {
ArrayList<EventDisplayInfo> eventList = new ArrayList<EventDisplayInfo>();
HashSet<FlightEvent.Type> typeSet = new HashSet<FlightEvent.Type>();
double prevTime = -100;
String text = null;
Color color = null;
Image image = null;
for ( int branch=0; branch<branchCount; branch++ ) {
List<FlightEvent> events = simulation.getSimulatedData().getBranch(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 + ", " + type.toString();
color = EventGraphics.getEventColor(type);
image = EventGraphics.getEventImage(type);
typeSet.add(type);
}
} else {
if (text != null) {
EventDisplayInfo info = new EventDisplayInfo();
info.time = prevTime;
info.event = text;
info.color = color;
info.image = image;
eventList.add(info);
}
prevTime = t;
text = type.toString();
color = EventGraphics.getEventColor(type);
image = EventGraphics.getEventImage(type);
typeSet.clear();
typeSet.add(type);
}
}
}
}
if (text != null) {
EventDisplayInfo info = new EventDisplayInfo();
info.time = prevTime;
info.event = text;
info.color = color;
info.image = image;
eventList.add(info);
}
return eventList;
}
/**
* A modification to the standard renderer that renders the domain marker
* labels vertically instead of horizontally.
*
* This class is special in that it assumes the data series are added to it
* in a specific order. In particular they must be "by parameter by stage".
* Assuming that three series are chosen (a, b, c) and the rocket has 2 stages, the
* data series are added in this order:
*
* series a stage 0
* series a stage 1
* series b stage 0
* series b stage 1
* series c stage 0
* series c stage 1
*/
private static class ModifiedXYItemRenderer extends XYLineAndShapeRenderer {
private final int branchCount;
private ModifiedXYItemRenderer( int branchCount ) {
this.branchCount = branchCount;
}
@Override
public Paint lookupSeriesPaint(int series) {
return super.lookupSeriesPaint(series/branchCount);
}
@Override
public Paint lookupSeriesFillPaint(int series) {
return super.lookupSeriesFillPaint(series/branchCount);
}
@Override
public Paint lookupSeriesOutlinePaint(int series) {
return super.lookupSeriesOutlinePaint(series/branchCount);
}
@Override
public Stroke lookupSeriesStroke(int series) {
return super.lookupSeriesStroke(series/branchCount);
}
@Override
public Stroke lookupSeriesOutlineStroke(int series) {
return super.lookupSeriesOutlineStroke(series/branchCount);
}
@Override
public Shape lookupSeriesShape(int series) {
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series%branchCount%DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
}
@Override
public Shape lookupLegendShape(int series) {
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series%branchCount%DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
}
@Override
public Font lookupLegendTextFont(int series) {
return super.lookupLegendTextFont(series/branchCount);
}
@Override
public Paint lookupLegendTextPaint(int series) {
return super.lookupLegendTextPaint(series/branchCount);
}
@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.
*/
ValueMarker vm = (ValueMarker) marker;
double value = vm.getValue();
Range range = domainAxis.getRange();
if (!range.contains(value)) {
return;
}
double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
PlotOrientation orientation = plot.getOrientation();
Line2D line = null;
if (orientation == PlotOrientation.HORIZONTAL) {
line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
} 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) {
Font labelFont = marker.getLabelFont();
g2.setFont(labelFont);
g2.setPaint(marker.getLabelPaint());
Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
orientation, dataArea, line.getBounds2D(), marker
.getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
// Changed:
TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
(float) coordinates.getY(), textAnchor,
-Math.PI / 2, textAnchor);
}
g2.setComposite(originalComposite);
}
}
private static class PresetNumberAxis extends NumberAxis {
private final double min;
private final double max;
public PresetNumberAxis(double min, double max) {
this.min = min;
this.max = max;
autoAdjustRange();
}
@Override
protected void autoAdjustRange() {
this.setRange(min, max);
}
}
private static class EventDisplayInfo {
double time;
String event;
Color color;
Image image;
}
}

View File

@ -14,6 +14,8 @@ import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
@ -30,6 +32,7 @@ import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
@ -61,6 +64,7 @@ import org.jfree.chart.plot.Marker;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.Range;
@ -78,65 +82,8 @@ import org.jfree.ui.TextAnchor;
*/
public class SimulationPlotDialog extends JDialog {
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>();
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));
}
private static final Map<FlightEvent.Type, Image> EVENT_IMAGES = new HashMap<FlightEvent.Type, Image>();
static {
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/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/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 final List<ModifiedXYItemRenderer> renderers =
new ArrayList<ModifiedXYItemRenderer>();
private SimulationPlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
//// Flight data plot
super(parent, simulation.getName());
@ -144,292 +91,18 @@ public class SimulationPlotDialog extends JDialog {
final boolean initialShowPoints = Application.getPreferences().getBoolean(Preferences.PLOT_SHOW_POINTS, false);
List<Integer> selectedBranches = config.getSelectedBranches();
// Fill the auto-selections based on first branch selected.
FlightDataBranch mainBranch = simulation.getSimulatedData().getBranch( selectedBranches.get(0));
PlotConfiguration filled = config.fillAutoAxes(mainBranch);
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 FlightDataType domainType = filled.getDomainAxisType();
final Unit domainUnit = filled.getDomainAxisUnit();
if (domainType == null) {
throw new IllegalArgumentException("Domain axis type not specified.");
}
// Get plot length (ignore trailing NaN's)
int typeCount = filled.getTypeCount();
// 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
FlightDataType type = filled.getType(i);
Unit unit = filled.getUnit(i);
int axis = filled.getAxis(i);
String name = getLabel(type, unit);
for( int branchCount: selectedBranches ) {
FlightDataBranch thisBranch = simulation.getSimulatedData().getBranch(branchCount);
// Store data in provided units
List<Double> plotx = thisBranch.get(domainType);
List<Double> ploty = thisBranch.get(type);
XYSeries series = new XYSeries(thisBranch.getBranchName() + "-" + branchCount+": " + name, false, true);
int pointCount = plotx.size();
for (int j = 0; j < pointCount; j++) {
series.add(domainUnit.toUnit(plotx.get(j)), unit.toUnit(ploty.get(j)));
}
data[axis].addSeries(series);
}
// Update axis label
if (axisLabel[axis] == null)
axisLabel[axis] = type.getName();
else
axisLabel[axis] += "; " + type.getName();
}
// Create the chart using the factory to get all default settings
JFreeChart chart = ChartFactory.createXYLineChart(
//// Simulated flight
simulation.getName(),
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();
plot.setDomainPannable(true);
plot.setRangePannable(true);
int axisno = 0;
for (int i = 0; i < 2; i++) {
// Check whether axis has any data
if (data[i].getSeriesCount() > 0) {
// Create and set axis
double min = axes.get(i).getMinValue();
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());
plot.setRangeAxis(axisno, axis);
// Add data and map to the axis
plot.setDataset(axisno, data[i]);
ModifiedXYItemRenderer r = new ModifiedXYItemRenderer(selectedBranches.size());
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);
axisno++;
}
}
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>();
ArrayList<Color> colorList = new ArrayList<Color>();
ArrayList<Image> imageList = new ArrayList<Image>();
HashSet<FlightEvent.Type> typeSet = new HashSet<FlightEvent.Type>();
double prevTime = -100;
String text = null;
Color color = null;
Image image = null;
for ( int branch : selectedBranches ) {
List<FlightEvent> events = simulation.getSimulatedData().getBranch(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 + ", " + type.toString();
color = getEventColor(type);
image = EVENT_IMAGES.get(type);
typeSet.add(type);
}
} else {
if (text != null) {
timeList.add(prevTime);
eventList.add(text);
colorList.add(color);
imageList.add(image);
}
prevTime = t;
text = type.toString();
color = getEventColor(type);
image = EVENT_IMAGES.get(type);
typeSet.clear();
typeSet.add(type);
}
}
}
}
if (text != null) {
timeList.add(prevTime);
eventList.add(text);
colorList.add(color);
imageList.add(image);
}
// Create the event markers
if (config.getDomainAxisType() == FlightDataType.TYPE_TIME) {
// Domain time is plotted as vertical markers
for (int i = 0; i < eventList.size(); i++) {
double t = timeList.get(i);
String event = eventList.get(i);
color = colorList.get(i);
ValueMarker m = new ValueMarker(t);
m.setLabel(event);
m.setPaint(color);
m.setLabelPaint(color);
m.setAlpha(0.7f);
plot.addDomainMarker(m);
}
} else {
// Other domains are plotted as image annotations
List<Double> time = mainBranch.get(FlightDataType.TYPE_TIME);
List<Double> domain = mainBranch.get(config.getDomainAxisType());
for (int i = 0; i < eventList.size(); i++) {
final double t = timeList.get(i);
String event = eventList.get(i);
image = imageList.get(i);
if (image == null)
continue;
// Calculate index and interpolation position a
final double a;
int tindex = Collections.binarySearch(time, t);
if (tindex < 0) {
tindex = -tindex - 1;
}
if (tindex >= time.size()) {
// index greater than largest value in time list
tindex = time.size() - 1;
a = 0;
} else if (tindex <= 0) {
// index smaller than smallest value in time list
tindex = 0;
a = 0;
} else {
tindex--;
double t1 = time.get(tindex);
double t2 = time.get(tindex + 1);
if ((t1 > t) || (t2 < t)) {
throw new BugException("t1=" + t1 + " t2=" + t2 + " t=" + t);
}
if (MathUtil.equals(t1, t2)) {
a = 0;
} else {
a = 1 - (t - t1) / (t2 - t1);
}
}
double xcoord;
if (a == 0) {
xcoord = domain.get(tindex);
} else {
xcoord = a * domain.get(tindex) + (1 - a) * domain.get(tindex + 1);
}
for (int index = 0; index < config.getTypeCount(); index++) {
FlightDataType type = config.getType(index);
List<Double> range = mainBranch.get(type);
// 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);
}
// 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);
}
}
}
final SimulationPlot myPlot = new SimulationPlot(simulation, config, initialShowPoints );
// Create the dialog
JPanel panel = new JPanel(new MigLayout("fill"));
this.add(panel);
final ChartPanel chartPanel = new SimulationChart(chart,
false, // properties
true, // save
false, // print
true, // zoom
true); // tooltips
chartPanel.setMouseWheelEnabled(true);
chartPanel.setEnforceFileExtensions(true);
chartPanel.setInitialDelay(500);
chartPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 1));
final ChartPanel chartPanel = new SimulationChart(myPlot.getJFreeChart());
panel.add(chartPanel, "grow, wrap 20lp");
//// Description text
JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
panel.add(label, "gapleft para");
panel.add(label, "wrap");
//// Show data points
final JCheckBox check = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
@ -439,9 +112,7 @@ public class SimulationPlotDialog extends JDialog {
public void actionPerformed(ActionEvent e) {
boolean show = check.isSelected();
Application.getPreferences().putBoolean(Preferences.PLOT_SHOW_POINTS, show);
for (ModifiedXYItemRenderer r : renderers) {
r.setBaseShapesVisible(show);
}
myPlot.setShowPoints(show);
}
});
panel.add(check, "split, left");
@ -477,9 +148,25 @@ public class SimulationPlotDialog extends JDialog {
//// Add series selection box
//// FIXME
panel.add(new JPanel(), "growx");
List<String> stages = new ArrayList<String>();
stages.add("All");
for ( int i=0; i< simulation.getSimulatedData().getBranchCount(); i++ ) {
stages.add( simulation.getSimulatedData().getBranch(i).getBranchName() + " (" + i + ")");
}
final JComboBox<String> stageSelection = new JComboBox<String>(stages.toArray(new String[0]));
stageSelection.addItemListener( new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
int selectedStage = stageSelection.getSelectedIndex() -1;
myPlot.setShowBranch(selectedStage);
}
});
if ( stages.size() > 1 ) {
panel.add(stageSelection, "gapleft rel");
panel.add(new JPanel(), "growx");
}
//// Close button
button = new JButton(trans.get("dlg.but.close"));
button.addActionListener(new ActionListener() {
@ -497,31 +184,6 @@ public class SimulationPlotDialog extends JDialog {
GUIUtil.rememberWindowSize(this);
}
private String getLabel(FlightDataType type, Unit unit) {
String name = type.getName();
if (unit != null && !UnitGroup.UNITS_NONE.contains(unit) &&
!UnitGroup.UNITS_COEFFICIENT.contains(unit) && unit.getUnit().length() > 0)
name += " (" + unit.getUnit() + ")";
return name;
}
private class PresetNumberAxis extends NumberAxis {
private final double min;
private final double max;
public PresetNumberAxis(double min, double max) {
this.min = min;
this.max = max;
autoAdjustRange();
}
@Override
protected void autoAdjustRange() {
this.setRange(min, max);
}
}
/**
@ -535,159 +197,4 @@ public class SimulationPlotDialog extends JDialog {
new SimulationPlotDialog(parent, simulation, config).setVisible(true);
}
private static Color getEventColor(FlightEvent.Type type) {
Color c = EVENT_COLORS.get(type);
if (c != null)
return c;
return DEFAULT_EVENT_COLOR;
}
/**
* A modification to the standard renderer that renders the domain marker
* labels vertically instead of horizontally.
*/
private static class ModifiedXYItemRenderer extends XYLineAndShapeRenderer {
private final int branchCount;
private ModifiedXYItemRenderer( int branchCount ) {
this.branchCount = branchCount;
}
@Override
public Paint lookupSeriesPaint(int series) {
return super.lookupSeriesPaint(series/branchCount);
}
@Override
public Paint lookupSeriesFillPaint(int series) {
// TODO Auto-generated method stub
return super.lookupSeriesFillPaint(series/branchCount);
}
@Override
public Paint lookupSeriesOutlinePaint(int series) {
// TODO Auto-generated method stub
return super.lookupSeriesOutlinePaint(series/branchCount);
}
@Override
public Stroke lookupSeriesStroke(int series) {
// TODO Auto-generated method stub
return super.lookupSeriesStroke(series/branchCount);
}
@Override
public Stroke lookupSeriesOutlineStroke(int series) {
// TODO Auto-generated method stub
return super.lookupSeriesOutlineStroke(series/branchCount);
}
@Override
public Shape lookupSeriesShape(int series) {
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series%branchCount%DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
}
@Override
public Shape lookupLegendShape(int series) {
return DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE[series%branchCount%DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE.length];
}
@Override
public Font lookupLegendTextFont(int series) {
// TODO Auto-generated method stub
return super.lookupLegendTextFont(series/branchCount);
}
@Override
public Paint lookupLegendTextPaint(int series) {
// TODO Auto-generated method stub
return super.lookupLegendTextPaint(series/branchCount);
}
@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.
*/
ValueMarker vm = (ValueMarker) marker;
double value = vm.getValue();
Range range = domainAxis.getRange();
if (!range.contains(value)) {
return;
}
double v = domainAxis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
PlotOrientation orientation = plot.getOrientation();
Line2D line = null;
if (orientation == PlotOrientation.HORIZONTAL) {
line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), v);
} 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) {
Font labelFont = marker.getLabelFont();
g2.setFont(labelFont);
g2.setPaint(marker.getLabelPaint());
Point2D coordinates = calculateDomainMarkerTextAnchorPoint(g2,
orientation, dataArea, line.getBounds2D(), marker
.getLabelOffset(), LengthAdjustmentType.EXPAND, anchor);
// Changed:
TextAnchor textAnchor = TextAnchor.TOP_RIGHT;
TextUtilities.drawRotatedString(label, g2, (float) coordinates.getX() + 2,
(float) coordinates.getY(), textAnchor,
-Math.PI / 2, textAnchor);
}
g2.setComposite(originalComposite);
}
}
}

View File

@ -109,17 +109,6 @@ public class SimulationPlotPanel extends JPanel {
setConfiguration(defaultConfiguration);
// FIXME - hard coding selected branches:
{
int branchCount = simulation.getSimulatedData().getBranchCount();
ArrayList<Integer> selectedBranches = new ArrayList<Integer>(branchCount);
for( int i=0; i< branchCount; i++ ) {
selectedBranches.add(i);
}
configuration.setSelectedBranches(selectedBranches);
}
//// Configuration selector
// Setup the combo box