Add SIM_WARN events to plots.
Note that this required a lot of rewrite to the code adding event icons
This commit is contained in:
parent
67f6aa191a
commit
38a3772eb0
@ -184,6 +184,13 @@ public class FlightEvent implements Comparable<FlightEvent> {
|
||||
return this.type.ordinal() - o.type.ordinal();
|
||||
}
|
||||
|
||||
public boolean equals(FlightEvent o) {
|
||||
if ((this.type == Type.SIM_WARN) && (o.type == Type.SIM_WARN))
|
||||
return ((Warning)(this.data)).equals((Warning)(o.data));
|
||||
|
||||
return this.equals(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FlightEvent[type=" + type.name() + ",time=" + time + ",source=" + source + ",data=" + String.valueOf(data) + "]";
|
||||
|
@ -101,8 +101,8 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
|
||||
|
||||
// Create the data series for both axes
|
||||
this.data = new XYSeriesCollection[2];
|
||||
this.data[0] = new XYSeriesCollection();
|
||||
this.data[1] = new XYSeriesCollection();
|
||||
this.data[Util.PlotAxisSelection.LEFT.getValue()] = new XYSeriesCollection();
|
||||
this.data[Util.PlotAxisSelection.RIGHT.getValue()] = new XYSeriesCollection();
|
||||
|
||||
// Fill the auto-selections based on first branch selected.
|
||||
this.filledConfig = config.fillAutoAxes(mainBranch);
|
||||
@ -278,7 +278,7 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
|
||||
protected List<XYSeries> createSeriesForType(int dataIndex, int startIndex, T type, Unit unit, B branch,
|
||||
int branchIdx, String branchName, String baseName) {
|
||||
// Default implementation for regular DataBranch
|
||||
MetadataXYSeries series = new MetadataXYSeries(startIndex, false, true, branchIdx, unit.getUnit(), branchName, baseName);
|
||||
MetadataXYSeries series = new MetadataXYSeries(startIndex, false, true, branchIdx, dataIndex, unit.getUnit(), branchName, baseName);
|
||||
|
||||
List<Double> plotx = branch.get(filledConfig.getDomainAxisType());
|
||||
List<Double> ploty = branch.get(type);
|
||||
@ -327,7 +327,7 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
|
||||
return formatSampleTooltip(dataName, dataX, unitX, 0, "", sampleIdx, false);
|
||||
}
|
||||
|
||||
private String getOrdinalEnding(int n) {
|
||||
protected String getOrdinalEnding(int n) {
|
||||
if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) return "th";
|
||||
return switch (n % 10) {
|
||||
case 1 -> "st";
|
||||
@ -540,20 +540,27 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
|
||||
|
||||
protected static class MetadataXYSeries extends XYSeries {
|
||||
private final int branchIdx;
|
||||
private final int dataIdx;
|
||||
private final String unit;
|
||||
private final String branchName;
|
||||
private String baseName;
|
||||
|
||||
public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, String unit,
|
||||
public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, int dataIdx, String unit,
|
||||
String branchName, String baseName) {
|
||||
super(key, autoSort, allowDuplicateXValues);
|
||||
this.branchIdx = branchIdx;
|
||||
this.dataIdx = dataIdx;
|
||||
this.unit = unit;
|
||||
this.branchName = branchName;
|
||||
this.baseName = baseName;
|
||||
updateDescription();
|
||||
}
|
||||
|
||||
public MetadataXYSeries(Comparable key, boolean autoSort, boolean allowDuplicateXValues, int branchIdx, String unit,
|
||||
String branchName, String baseName) {
|
||||
this(key, autoSort, allowDuplicateXValues, branchIdx, -1, unit, branchName, baseName);
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
@ -562,6 +569,10 @@ public abstract class Plot<T extends DataType, B extends DataBranch<T>, C extend
|
||||
return branchIdx;
|
||||
}
|
||||
|
||||
public int getDataIdx() {
|
||||
return dataIdx;
|
||||
}
|
||||
|
||||
public String getBranchName() {
|
||||
return branchName;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -13,11 +14,13 @@ import java.util.Set;
|
||||
|
||||
import info.openrocket.core.document.Simulation;
|
||||
import info.openrocket.core.logging.SimulationAbort;
|
||||
import info.openrocket.core.logging.Warning;
|
||||
import info.openrocket.core.simulation.FlightDataBranch;
|
||||
import info.openrocket.core.simulation.FlightDataType;
|
||||
import info.openrocket.core.simulation.FlightEvent;
|
||||
import info.openrocket.core.preferences.ApplicationPreferences;
|
||||
import info.openrocket.core.util.LinearInterpolator;
|
||||
import info.openrocket.swing.utils.DecimalFormatter;
|
||||
|
||||
import org.jfree.chart.annotations.XYImageAnnotation;
|
||||
import org.jfree.chart.annotations.XYTitleAnnotation;
|
||||
@ -31,6 +34,8 @@ import org.jfree.chart.ui.VerticalAlignment;
|
||||
import org.jfree.chart.ui.RectangleAnchor;
|
||||
import org.jfree.chart.ui.RectangleEdge;
|
||||
import org.jfree.chart.ui.RectangleInsets;
|
||||
import org.jfree.data.xy.XYSeries;
|
||||
import org.jfree.data.xy.XYSeriesCollection;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, SimulationPlotConfiguration> {
|
||||
@ -106,32 +111,30 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul
|
||||
plot.clearAnnotations();
|
||||
|
||||
// Store flight event information
|
||||
List<Double> eventTimes = new ArrayList<>();
|
||||
List<String> eventLabels = new ArrayList<>();
|
||||
List<Color> eventColors = new ArrayList<>();
|
||||
List<Image> eventImages = new ArrayList<>();
|
||||
List<Set<FlightEvent>> eventSets = new ArrayList<>();
|
||||
|
||||
// Plot the markers
|
||||
if (config.getDomainAxisType() == FlightDataType.TYPE_TIME && !preferences.getBoolean(ApplicationPreferences.MARKER_STYLE_ICON, false)) {
|
||||
fillEventLists(branch, eventTimes, eventLabels, eventColors, eventImages);
|
||||
plotVerticalLineMarkers(plot, eventTimes, eventLabels, eventColors);
|
||||
fillEventLists(branch, eventColors, eventImages, eventSets);
|
||||
plotVerticalLineMarkers(plot, eventColors, eventSets);
|
||||
|
||||
} else { // Other domains are plotted as image annotations
|
||||
if (branch == -1) {
|
||||
// For icon markers, we need to do the plotting separately, otherwise you can have icon markers from e.g.
|
||||
// branch 1 be plotted on branch 0
|
||||
for (int b = 0; b < simulation.getSimulatedData().getBranchCount(); b++) {
|
||||
fillEventLists(b, eventTimes, eventLabels, eventColors, eventImages);
|
||||
fillEventLists(b, eventColors, eventImages, eventSets);
|
||||
dataBranch = simulation.getSimulatedData().getBranch(b);
|
||||
plotIconMarkers(plot, dataBranch, eventTimes, eventLabels, eventImages);
|
||||
eventTimes.clear();
|
||||
eventLabels.clear();
|
||||
plotIconMarkers(plot, simulation, b, eventImages, eventSets);
|
||||
eventSets = new ArrayList<>();
|
||||
eventColors.clear();
|
||||
eventImages.clear();
|
||||
}
|
||||
} else {
|
||||
fillEventLists(branch, eventTimes, eventLabels, eventColors, eventImages);
|
||||
plotIconMarkers(plot, dataBranch, eventTimes, eventLabels, eventImages);
|
||||
fillEventLists(branch, eventColors, eventImages, eventSets);
|
||||
plotIconMarkers(plot, simulation, branch, eventImages, eventSets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,72 +163,86 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul
|
||||
newBranchName.append(" + ").append(allBranches.get(i).getName());
|
||||
}
|
||||
}
|
||||
|
||||
return newBranchName + ": " + ser.getBaseName();
|
||||
}
|
||||
|
||||
private void fillEventLists(int branch, List<Double> eventTimes, List<String> eventLabels,
|
||||
List<Color> eventColors, List<Image> eventImages) {
|
||||
private void fillEventLists(int branch,
|
||||
List<Color> eventColors, List<Image> eventImages, List<Set<FlightEvent>> eventSets) {
|
||||
Set<FlightEvent> eventSet = new HashSet<>();
|
||||
Set<FlightEvent.Type> typeSet = new HashSet<>();
|
||||
double prevTime = -100;
|
||||
String text = null;
|
||||
Color color = null;
|
||||
Image image = null;
|
||||
int maxOrdinal = -1;
|
||||
|
||||
for (EventDisplayInfo info : eventList) {
|
||||
if (branch >= 0 && branch != info.stage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double t = info.time;
|
||||
FlightEvent.Type type = info.event.getType();
|
||||
FlightEvent event = info.event;
|
||||
FlightEvent.Type type = event.getType();
|
||||
|
||||
if (Math.abs(t - prevTime) <= 0.05) {
|
||||
if (!typeSet.contains(type)) {
|
||||
text = text + ", " + type.toString();
|
||||
if (type.ordinal() > maxOrdinal) {
|
||||
color = EventGraphics.getEventColor(info.event);
|
||||
image = EventGraphics.getEventImage(info.event);
|
||||
color = EventGraphics.getEventColor(event);
|
||||
image = EventGraphics.getEventImage(event);
|
||||
maxOrdinal = type.ordinal();
|
||||
}
|
||||
typeSet.add(type);
|
||||
eventSet.add(event);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (text != null) {
|
||||
eventTimes.add(prevTime);
|
||||
eventLabels.add(text);
|
||||
if (!eventSet.isEmpty()) {
|
||||
eventColors.add(color);
|
||||
eventImages.add(image);
|
||||
eventSets.add(eventSet);
|
||||
}
|
||||
prevTime = t;
|
||||
text = type.toString();
|
||||
color = EventGraphics.getEventColor(info.event);
|
||||
image = EventGraphics.getEventImage(info.event);
|
||||
color = EventGraphics.getEventColor(event);
|
||||
image = EventGraphics.getEventImage(event);
|
||||
typeSet.clear();
|
||||
typeSet.add(type);
|
||||
eventSet = new HashSet<>();
|
||||
eventSet.add(event);
|
||||
maxOrdinal = type.ordinal();
|
||||
}
|
||||
|
||||
}
|
||||
if (text != null) {
|
||||
eventTimes.add(prevTime);
|
||||
eventLabels.add(text);
|
||||
if (!eventSet.isEmpty()) {
|
||||
eventColors.add(color);
|
||||
eventImages.add(image);
|
||||
eventSets.add(eventSet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void plotVerticalLineMarkers(XYPlot plot, List<Double> eventTimes, List<String> eventLabels, List<Color> eventColors) {
|
||||
private static String constructEventLabels(Set<FlightEvent> events) {
|
||||
String text = "";
|
||||
|
||||
for (FlightEvent event : events) {
|
||||
if (text != "") {
|
||||
text += ", ";
|
||||
}
|
||||
text += event.getType().toString();
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private static void plotVerticalLineMarkers(XYPlot plot, List<Color> eventColors, List<Set<FlightEvent>> eventSets) {
|
||||
double markerWidth = 0.01 * plot.getDomainAxis().getUpperBound();
|
||||
|
||||
// Domain time is plotted as vertical lines
|
||||
for (int i = 0; i < eventTimes.size(); i++) {
|
||||
double t = eventTimes.get(i);
|
||||
String event = eventLabels.get(i);
|
||||
for (int i = 0; i < eventSets.size(); i++) {
|
||||
Set<FlightEvent> events = eventSets.get(i);
|
||||
double t = ((FlightEvent)events.toArray()[0]).getTime();
|
||||
String eventLabel = constructEventLabels(events);
|
||||
Color color = eventColors.get(i);
|
||||
|
||||
ValueMarker m = new ValueMarker(t);
|
||||
m.setLabel(event);
|
||||
m.setLabel(eventLabel);
|
||||
m.setPaint(color);
|
||||
m.setLabelPaint(color);
|
||||
m.setAlpha(0.7f);
|
||||
@ -238,40 +255,60 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul
|
||||
}
|
||||
}
|
||||
|
||||
private void plotIconMarkers(XYPlot plot, FlightDataBranch dataBranch, List<Double> eventTimes,
|
||||
List<String> eventLabels, List<Image> eventImages) {
|
||||
private void plotIconMarkers(XYPlot plot, Simulation simulation, int branch, List<Image> eventImages, List<Set<FlightEvent>> eventSets) {
|
||||
|
||||
FlightDataBranch dataBranch = simulation.getSimulatedData().getBranch(branch);
|
||||
|
||||
List<Double> time = dataBranch.get(FlightDataType.TYPE_TIME);
|
||||
List<Double> domain = dataBranch.get(config.getDomainAxisType());
|
||||
|
||||
LinearInterpolator domainInterpolator = new LinearInterpolator(time, domain);
|
||||
String xName = config.getDomainAxisType().getName();
|
||||
|
||||
for (int i = 0; i < eventTimes.size(); i++) {
|
||||
double t = eventTimes.get(i);
|
||||
List<Axis> minMaxAxes = filledConfig.getAllAxes();
|
||||
|
||||
for (int axisno = 0; axisno < data.length; axisno++) {
|
||||
// Image annotations are drawn using the data space defined by the left axis, so
|
||||
// the position of annotations on the right axis need to be mapped to the left axis
|
||||
// dataspace.
|
||||
|
||||
double minLeft = minMaxAxes.get(0).getMinValue();
|
||||
double maxLeft = minMaxAxes.get(0).getMaxValue();
|
||||
|
||||
double minThis = minMaxAxes.get(axisno).getMinValue();
|
||||
double maxThis = minMaxAxes.get(axisno).getMaxValue();
|
||||
|
||||
double slope = (maxLeft - minLeft)/(maxThis - minThis);
|
||||
double intercept = (maxThis * minLeft - maxLeft * minThis)/(maxThis - minThis);
|
||||
|
||||
XYSeriesCollection collection = data[axisno];
|
||||
for (XYSeries series : (List<XYSeries>)(collection.getSeries())) {
|
||||
Plot.MetadataXYSeries metaSeries = (Plot.MetadataXYSeries) series;
|
||||
|
||||
if (metaSeries.getBranchIdx() != branch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int dataTypeIdx = metaSeries.getDataIdx();
|
||||
FlightDataType type = config.getType(dataTypeIdx);
|
||||
String yName = type.toString();
|
||||
List<Double> range = dataBranch.get(type);
|
||||
LinearInterpolator rangeInterpolator = new LinearInterpolator(time, range);
|
||||
|
||||
for (int i = 0; i < eventSets.size(); i++) {
|
||||
Set<FlightEvent> events = eventSets.get(i);
|
||||
double t = ((FlightEvent)events.toArray()[0]).getTime();
|
||||
Image image = eventImages.get(i);
|
||||
|
||||
if (image == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double xcoord = domainInterpolator.getValue(t);
|
||||
|
||||
for (int index = 0; index < config.getDataCount(); index++) {
|
||||
FlightDataType type = config.getType(index);
|
||||
List<Double> range = dataBranch.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 (filledConfig.getAxis(index) != Util.PlotAxisSelection.LEFT.getValue()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double ycoord = rangeInterpolator.getValue(t);
|
||||
if (!Double.isNaN(ycoord)) {
|
||||
// Convert units
|
||||
xcoord = config.getDomainAxisUnit().toUnit(xcoord);
|
||||
ycoord = config.getUnit(index).toUnit(ycoord);
|
||||
|
||||
xcoord = config.getDomainAxisUnit().toUnit(xcoord);
|
||||
ycoord = config.getUnit(dataTypeIdx).toUnit(ycoord);
|
||||
|
||||
if (!Double.isNaN(ycoord)) {
|
||||
// Get the sample index of the flight event. Because this can be an interpolation between two samples,
|
||||
// take the closest sample.
|
||||
final int sampleIdx;
|
||||
@ -279,16 +316,71 @@ public class SimulationPlot extends Plot<FlightDataType, FlightDataBranch, Simul
|
||||
.min(Comparator.comparingDouble(sample -> Math.abs(sample - t)));
|
||||
sampleIdx = closestSample.map(time::indexOf).orElse(-1);
|
||||
|
||||
String tooltipText = formatSampleTooltip(eventLabels.get(i), xcoord, config.getDomainAxisUnit().getUnit(), sampleIdx) ;
|
||||
// Convert units
|
||||
String unitY = metaSeries.getUnit();
|
||||
String unitX = config.getDomainAxisUnit().getUnit();
|
||||
|
||||
|
||||
String tooltipText = formatEventTooltip(getNameBasedOnIdxAndSeries(metaSeries, sampleIdx), events,
|
||||
xName, xcoord, unitX,
|
||||
yName, ycoord, unitY,
|
||||
sampleIdx) ;
|
||||
double yloc = slope * ycoord + intercept;
|
||||
XYImageAnnotation annotation =
|
||||
new XYImageAnnotation(xcoord, ycoord, image, RectangleAnchor.CENTER);
|
||||
new XYImageAnnotation(xcoord, yloc, image, RectangleAnchor.CENTER);
|
||||
annotation.setToolTipText(tooltipText);
|
||||
plot.addAnnotation(annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected String formatEventTooltip(String dataName, Set<FlightEvent> events, String xName, double dataX, String unitX, String yName, double dataY, String unitY, int sampleIdx) {
|
||||
String ord_end = getOrdinalEnding(sampleIdx);
|
||||
|
||||
DecimalFormat df_y = DecimalFormatter.df(dataY, 2, false);
|
||||
DecimalFormat df_x = DecimalFormatter.df(dataX, 2, false);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// start tooltip
|
||||
sb.append("<html>");
|
||||
|
||||
// Branchname(s)
|
||||
sb.append(String.format("<b><i>%s</i></b><br>", dataName));
|
||||
|
||||
// Any events?
|
||||
if ((null != events) && (events.size() != 0)) {
|
||||
// Pass through and collect any warnings
|
||||
for (FlightEvent event : events) {
|
||||
if (event.getType() == FlightEvent.Type.SIM_WARN) {
|
||||
sb.append("<b><i>Warning: " + ((Warning) event.getData()).toString() + "</b></i><br>");
|
||||
}
|
||||
}
|
||||
|
||||
// Now pass through and collect the other events
|
||||
String eventStr = "";
|
||||
for (FlightEvent event : events) {
|
||||
if (event.getType() != FlightEvent.Type.SIM_WARN) {
|
||||
if (eventStr != "") {
|
||||
eventStr = eventStr + ", ";
|
||||
}
|
||||
eventStr = eventStr + event.getType();
|
||||
}
|
||||
}
|
||||
sb.append(eventStr + "<br>");
|
||||
}
|
||||
|
||||
sb.append(String.format("%s: %s %s<br>", xName, df_x.format(dataX), unitX));
|
||||
sb.append(String.format("%s: %s %s<br>", yName, df_y.format(dataY), unitY));
|
||||
sb.append(String.format("%d<sup>%s</sup> sample", sampleIdx, ord_end));
|
||||
|
||||
// End tooltip
|
||||
sb.append("</html>");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private List<EventDisplayInfo> buildEventInfo() {
|
||||
ArrayList<EventDisplayInfo> eventList = new ArrayList<>();
|
||||
|
Loading…
x
Reference in New Issue
Block a user