package net.sf.openrocket.gui.dialogs.optimization;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.unit.Value;
import net.sf.openrocket.util.LinearInterpolator;
import net.sf.openrocket.util.MathUtil;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYBoxAnnotation;
import org.jfree.chart.annotations.XYLineAnnotation;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.CustomXYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.PaintScale;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.renderer.xy.XYShapeRenderer;
import org.jfree.chart.title.PaintScaleLegend;
import org.jfree.data.xy.DefaultXYZDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.TextAnchor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that plots the path of an optimization.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class OptimizationPlotDialog extends JDialog {
private static final Logger log = LoggerFactory.getLogger(OptimizationPlotDialog.class);
private static final Translator trans = Application.getTranslator();
private static final LinearInterpolator RED = new LinearInterpolator(
new double[] { 0.0, 1.0 }, new double[] { 0.0, 1.0 }
);
private static final LinearInterpolator GREEN = new LinearInterpolator(
new double[] { 0.0, 1.0 }, new double[] { 0.0, 0.0 }
);
private static final LinearInterpolator BLUE = new LinearInterpolator(
new double[] { 0.0, 1.0 }, new double[] { 1.0, 0.0 }
);
private static final Color OUT_OF_DOMAIN_COLOR = Color.BLACK;
private static final Color PATH_COLOR = new Color(220, 0, 0);
public OptimizationPlotDialog(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit, Window parent) {
super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel(new MigLayout("fill"));
ChartPanel chart;
if (modifiers.size() == 1) {
chart = create1DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
} else if (modifiers.size() == 2) {
chart = create2DPlot(path, evaluations, modifiers, parameter, stabilityUnit);
} else {
throw new IllegalArgumentException("Invalid dimensionality, dim=" + modifiers.size());
}
chart.setBorder(BorderFactory.createLineBorder(Color.BLACK));
panel.add(chart, "span, grow, wrap para");
JLabel label = new StyledLabel(trans.get("lbl.zoomInstructions"), -2);
panel.add(label, "");
JButton close = new JButton(trans.get("button.close"));
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
OptimizationPlotDialog.this.setVisible(false);
}
});
panel.add(close, "right");
this.add(panel);
GUIUtil.setDisposableDialogOptions(this, close);
GUIUtil.rememberWindowSize(this);
}
/**
* Create a 1D plot of the optimization path.
*/
private ChartPanel create1DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
SimulationModifier modX = modifiers.get(0);
Unit xUnit = modX.getUnitGroup().getDefaultUnit();
Unit yUnit = parameter.getUnitGroup().getDefaultUnit();
// Create the optimization path (with autosort)
XYSeries series = new XYSeries(trans.get("plot1d.series"), true, true);
List<String> tooltips = new ArrayList<String>();
for (Point p : evaluations.keySet()) {
FunctionEvaluationData data = evaluations.get(p);
if (data != null) {
if (data.getParameterValue() != null) {
Value[] state = data.getState();
series.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(data.getParameterValue().getValue()));
tooltips.add(getTooltip(data, parameter));
}
} else {
log.error("Could not find evaluation data for point " + p);
}
}
String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
String yLabel = parameter.getName() + " / " + yUnit.getUnit();
JFreeChart chart = ChartFactory.createXYLineChart(
trans.get("plot1d.title"),
xLabel,
yLabel,
null,
PlotOrientation.VERTICAL,
false, // Legend
true, // Tooltips
false); // Urls
// Set the scale of the plot to the limits
double x1 = xUnit.toUnit(modX.getMinValue());
double x2 = xUnit.toUnit(modX.getMaxValue());
if (x1 < x2 - 0.0001) {
log.debug("Setting 1D plot domain axis x1=" + x1 + " x2=" + x2);
chart.getXYPlot().getDomainAxis().setRange(x1, x2);
} else {
log.warn("1D plot domain singular x1=" + x1 + " x2=" + x2 + ", not setting");
}
// Add lines to show optimization limits
XYLineAnnotation line = new XYLineAnnotation(x1, -1e19, x1, 1e19);
chart.getXYPlot().addAnnotation(line);
line = new XYLineAnnotation(x2, -1e19, x2, 1e19);
chart.getXYPlot().addAnnotation(line);
// Mark the optimum point
Point optimum = path.get(path.size() - 1);
FunctionEvaluationData data = evaluations.get(optimum);
if (data != null) {
if (data.getParameterValue() != null) {
Value[] state = data.getState();
double x = xUnit.toUnit(state[0].getValue());
double y = yUnit.toUnit(data.getParameterValue().getValue());
XYPointerAnnotation text = new XYPointerAnnotation(trans.get("plot.label.optimum"),
x, y, Math.PI / 2);
text.setTextAnchor(TextAnchor.TOP_LEFT);
chart.getXYPlot().addAnnotation(text);
}
} else {
log.error("Could not find evaluation data for point " + optimum);
}
XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
lineRenderer.setBaseShapesVisible(true);
lineRenderer.setSeriesShapesFilled(0, false);
//lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
lineRenderer.setSeriesPaint(0, PATH_COLOR);
lineRenderer.setUseOutlinePaint(true);
CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
tooltipGenerator.addToolTipSeries(tooltips);
lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
XYPlot plot = chart.getXYPlot();
plot.setDataset(0, new XYSeriesCollection(series));
plot.setRenderer(lineRenderer);
return new ChartPanel(chart);
}
/**
* Create a 2D plot of the optimization path.
*/
private ChartPanel create2DPlot(List<Point> path, Map<Point, FunctionEvaluationData> evaluations,
List<SimulationModifier> modifiers, OptimizableParameter parameter, UnitGroup stabilityUnit) {
Unit parameterUnit = parameter.getUnitGroup().getDefaultUnit();
SimulationModifier modX = modifiers.get(0);
SimulationModifier modY = modifiers.get(1);
Unit xUnit = modX.getUnitGroup().getDefaultUnit();
Unit yUnit = modY.getUnitGroup().getDefaultUnit();
// Create the optimization path dataset
XYSeries pathSeries = new XYSeries(trans.get("plot2d.path"), false, true);
List<String> pathTooltips = new ArrayList<String>();
for (Point p : path) {
FunctionEvaluationData data = evaluations.get(p);
if (data != null) {
Value[] state = data.getState();
pathSeries.add(xUnit.toUnit(state[0].getValue()), yUnit.toUnit(state[1].getValue()));
pathTooltips.add(getTooltip(data, parameter));
} else {
log.error("Could not find evaluation data for point " + p);
}
}
// Create evaluations dataset
double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
double[][] evals = new double[3][evaluations.size()];
List<String> evalTooltips = new ArrayList<String>();
Iterator<FunctionEvaluationData> iterator = evaluations.values().iterator();
for (int i = 0; i < evaluations.size(); i++) {
FunctionEvaluationData data = iterator.next();
Value param = data.getParameterValue();
double value;
if (param != null) {
value = parameterUnit.toUnit(data.getParameterValue().getValue());
} else {
value = Double.NaN;
}
Value[] state = data.getState();
evals[0][i] = xUnit.toUnit(state[0].getValue());
evals[1][i] = yUnit.toUnit(state[1].getValue());
evals[2][i] = value;
if (value < min) {
min = value;
}
if (value > max) {
max = value;
}
evalTooltips.add(getTooltip(data, parameter));
}
DefaultXYZDataset evalDataset = new DefaultXYZDataset();
evalDataset.addSeries(trans.get("plot2d.evals"), evals);
String xLabel = modX.getRelatedObject().toString() + ": " + modX.getName() + " / " + xUnit.getUnit();
String yLabel = modY.getRelatedObject().toString() + ": " + modY.getName() + " / " + yUnit.getUnit();
JFreeChart chart = ChartFactory.createXYLineChart(
trans.get("plot2d.title"),
xLabel,
yLabel,
null,
//evalDataset,
PlotOrientation.VERTICAL,
true, // Legend
true, // Tooltips
false); // Urls
// Set the scale of the plot to the limits
double x1 = xUnit.toUnit(modX.getMinValue());
double x2 = xUnit.toUnit(modX.getMaxValue());
double y1 = yUnit.toUnit(modY.getMinValue());
double y2 = yUnit.toUnit(modY.getMaxValue());
if (x1 < x2 - 0.0001) {
log.debug("Setting 2D plot domain axis to x1=" + x1 + " x2=" + x2);
chart.getXYPlot().getDomainAxis().setRange(x1, x2);
} else {
log.warn("2D plot has singular domain axis: x1=" + x1 + " x2=" + x2);
}
if (y1 < y2 - 0.0001) {
log.debug("Setting 2D plot range axis to y1=" + y1 + " y2=" + y2);
chart.getXYPlot().getRangeAxis().setRange(y1, y2);
} else {
log.warn("2D plot has singular range axis: y1=" + y1 + " y2=" + y2);
}
XYBoxAnnotation box = new XYBoxAnnotation(x1, y1, x2, y2);
chart.getXYPlot().addAnnotation(box);
int n = pathSeries.getItemCount();
XYPointerAnnotation text = new XYPointerAnnotation(trans.get("plot.label.optimum"),
(Double) pathSeries.getX(n - 1), (Double) pathSeries.getY(n - 1), -Math.PI / 5);
text.setTextAnchor(TextAnchor.BASELINE_LEFT);
chart.getXYPlot().addAnnotation(text);
if (min < max - 0.0001) {
log.debug("Setting gradient scale range to min=" + min + " max=" + max);
} else {
log.warn("2D plot has singular gradient scale, resetting to (0,1): min=" + min + " max=" + max);
min = 0;
max = 1;
}
PaintScale paintScale = new GradientScale(min, max);
XYShapeRenderer shapeRenderer = new XYShapeRenderer();
shapeRenderer.setPaintScale(paintScale);
shapeRenderer.setUseFillPaint(true);
CustomXYToolTipGenerator tooltipGenerator = new CustomXYToolTipGenerator();
tooltipGenerator.addToolTipSeries(evalTooltips);
shapeRenderer.setBaseToolTipGenerator(tooltipGenerator);
shapeRenderer.getLegendItem(0, 0);
XYLineAndShapeRenderer lineRenderer = new XYLineAndShapeRenderer(true, true);
lineRenderer.setBaseShapesVisible(true);
lineRenderer.setSeriesShapesFilled(0, false);
lineRenderer.setSeriesShape(0, shapeRenderer.getBaseShape());
lineRenderer.setSeriesOutlinePaint(0, PATH_COLOR);
lineRenderer.setSeriesPaint(0, PATH_COLOR);
lineRenderer.setUseOutlinePaint(true);
tooltipGenerator = new CustomXYToolTipGenerator();
tooltipGenerator.addToolTipSeries(pathTooltips);
lineRenderer.setBaseToolTipGenerator(tooltipGenerator);
XYPlot plot = chart.getXYPlot();
plot.setDataset(0, new XYSeriesCollection(pathSeries));
plot.setRenderer(lineRenderer);
plot.setDataset(1, evalDataset);
plot.setRenderer(1, shapeRenderer);
// Add value scale
NumberAxis numberAxis = new NumberAxis(parameter.getName() + " / " + parameterUnit.getUnit());
PaintScaleLegend scale = new PaintScaleLegend(paintScale, numberAxis);
scale.setPosition(RectangleEdge.RIGHT);
scale.setMargin(4.0D, 4.0D, 40.0D, 4.0D);
scale.setAxisLocation(AxisLocation.BOTTOM_OR_RIGHT);
chart.addSubtitle(scale);
return new ChartPanel(chart);
}
private String getTooltip(FunctionEvaluationData data, OptimizableParameter parameter) {
String ttip = "<html>";
if (data.getParameterValue() != null) {
ttip += parameter.getName() + ": " +
parameter.getUnitGroup().getDefaultUnit().toStringUnit(data.getParameterValue().getValue());
ttip += "<br>";
}
if (data.getDomainReference() != null) {
ttip += trans.get("plot.ttip.stability") + " " + data.getDomainReference();
}
return ttip;
}
private class GradientScale implements PaintScale {
private final double min;
private final double max;
public GradientScale(double min, double max) {
this.min = min;
this.max = max;
}
@Override
public Paint getPaint(double value) {
if (Double.isNaN(value)) {
return OUT_OF_DOMAIN_COLOR;
}
value = MathUtil.map(value, min, max, 0.0, 1.0);
value = MathUtil.clamp(value, 0.0, 1.0);
float r = (float) RED.getValue(value);
float g = (float) GREEN.getValue(value);
float b = (float) BLUE.getValue(value);
return new Color(r, g, b);
}
@Override
public double getLowerBound() {
return min;
}
@Override
public double getUpperBound() {
return max;
}
}
}