Java tutorial
/* Copyright 2009-2016 David Hadka * * This file is part of the MOEA Framework. * * The MOEA Framework is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * The MOEA Framework is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the MOEA Framework. If not, see <http://www.gnu.org/licenses/>. */ package org.moeaframework.analysis.plot; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.RenderingHints; import java.awt.Dialog.ModalityType; import java.awt.geom.Rectangle2D; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import javax.swing.JDialog; import javax.swing.JFrame; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.labels.BoxAndWhiskerToolTipGenerator; import org.jfree.chart.labels.StandardXYToolTipGenerator; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.category.BoxAndWhiskerRenderer; import org.jfree.chart.renderer.category.CategoryItemRenderer; import org.jfree.chart.renderer.xy.StackedXYAreaRenderer; import org.jfree.chart.renderer.xy.XYAreaRenderer; import org.jfree.chart.renderer.xy.XYDotRenderer; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.data.category.CategoryDataset; import org.jfree.data.statistics.DefaultBoxAndWhiskerCategoryDataset; import org.jfree.data.xy.DefaultTableXYDataset; import org.jfree.data.xy.XYDataset; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import org.moeaframework.Analyzer; import org.moeaframework.Analyzer.AnalyzerResults; import org.moeaframework.analysis.collector.Accumulator; import org.moeaframework.analysis.diagnostics.PaintHelper; import org.moeaframework.core.FrameworkException; import org.moeaframework.core.Population; import org.moeaframework.core.Solution; /** * Provides simple 2D plotting capabilities. This is intended to allow the * rapid creation of 2D plots, supporting: * <ol> * <li>scatter plots of bi-objective populations,</li> * <li>line plots of runtime dynamics (via an {@link Accumulator},</li> * <li>box-and-whisker plots of performance statistics (via an {@link Analyzer}), and</li> * <li>other plots of basic data types (e.g., line, scatter, area, stacked).</li> * </ol> * It is possible to combine datasets by calling more than one {@code add} * method, but you can not mix different plot types (e.g., XY plots versus * categorical plots). Thus, box-and-whisker plots can not be overlaid on a * line plot. * <p> * In general, one should first generate the plot artifacts by calling * {@code line}, {@code scatter}, {@code area}, {@code stacked}, or any of the * {@code add} methods. Artifacts can be customized by immediately calling * one of the {@code with*} methods after generating the artifact (the * customization is only applied to the last artifact). Lastly, call the * {@code set*} methods to customize the overall appearance of the chart. * Call {@code show} to display the chart in a window or {@code save} to * create an image file. For example: * <pre> * new Plot() * .scatter("Point", new double[] { 0.0, 1.0, 2.0 }, new double[] { 3.0, 4.0, 5.0 }) * .withPaint(Color.BLACK) * .line("Line", new double[] { 0.0, 2.0 }, new double[] { 3.0, 5.0 }) * .withSize(5) * .setXLabel("X") * .setYLabel("Y") * .setTitle("Example") * .show(); * </pre> * <p> * This class is not intended to be a fully featured plotting library. To * generate more sophisticated plots or customize their appearance, one must * instead use JFreeChart, JZY3D, or another Java plotting library. * <p> * Generated plots can be saved to PNG or JPEG files. If JFreeSVG is available * on the classpath, SVG files can be generated. JFreeSVG can be obtained from * http://www.jfree.org/jfreesvg/. */ public class Plot { /** * The internal JFreeChart instance. */ private JFreeChart chart; /** * Maps labels to their assigned color. */ private PaintHelper paintHelper; /** * The index of the current dataset. */ private int currentDataset; /** * Creates a new, empty plot. */ public Plot() { super(); paintHelper = new PaintHelper(); currentDataset = -1; } /** * If the chart has not yet been initialized, creates a chart for XY data. * If the chart is already initialized, checks if the chart is for XY data. * * @throws FrameworkException if the chart does not support XY data */ private void createXYPlot() { if (chart == null) { NumberAxis xAxis = new NumberAxis(""); xAxis.setAutoRangeIncludesZero(false); NumberAxis yAxis = new NumberAxis(""); yAxis.setAutoRangeIncludesZero(false); XYPlot plot = new XYPlot(); plot.setDomainAxis(xAxis); plot.setRangeAxis(yAxis); XYToolTipGenerator toolTipGenerator = new StandardXYToolTipGenerator(); XYItemRenderer renderer = new XYLineAndShapeRenderer(false, true); renderer.setBaseToolTipGenerator(toolTipGenerator); plot.setRenderer(renderer); plot.setOrientation(PlotOrientation.VERTICAL); chart = new JFreeChart("", JFreeChart.DEFAULT_TITLE_FONT, plot, true); ChartFactory.getChartTheme().apply(chart); } else if (!(chart.getPlot() instanceof XYPlot)) { throw new FrameworkException("Can not combine XY plot and categorial plot"); } } /** * If the chart has not yet been initialized, creates a chart for * categorical data. If the chart is already initialized, checks if the * chart is for categorical data. * * @throws FrameworkException if the chart does not support categorical data */ private void createCategoryPlot() { if (chart == null) { CategoryAxis xAxis = new CategoryAxis(""); NumberAxis yAxis = new NumberAxis("Value"); yAxis.setAutoRangeIncludesZero(false); final BoxAndWhiskerRenderer renderer = new BoxAndWhiskerRenderer(); renderer.setFillBox(true); renderer.setBaseToolTipGenerator(new BoxAndWhiskerToolTipGenerator()); final CategoryPlot plot = new CategoryPlot(); plot.setDomainAxis(xAxis); plot.setRangeAxis(yAxis); plot.setRenderer(renderer); chart = new JFreeChart("", JFreeChart.DEFAULT_TITLE_FONT, plot, true); ChartFactory.getChartTheme().apply(chart); } else if (!(chart.getPlot() instanceof CategoryPlot)) { throw new FrameworkException("Can not combine XY plot and categorial plot"); } } /** * Sets the chart title. * * @param title the title * @return a reference to this {@code Plot} instance */ public Plot setTitle(String title) { chart.setTitle(title); return this; } /** * Sets the x-axis label. * * @param label the label for the x-axis * @return a reference to this {@code Plot} instance */ public Plot setXLabel(String label) { if (chart.getPlot() instanceof XYPlot) { chart.getXYPlot().getDomainAxis().setLabel(label); } else if (chart.getPlot() instanceof CategoryPlot) { chart.getCategoryPlot().getDomainAxis().setLabel(label); } return this; } /** * Sets the y-axis label. * * @param label the label for the y-axis * @return a reference to this {@code Plot} instance */ public Plot setYLabel(String label) { if (chart.getPlot() instanceof XYPlot) { chart.getXYPlot().getRangeAxis().setLabel(label); } else if (chart.getPlot() instanceof CategoryPlot) { chart.getCategoryPlot().getRangeAxis().setLabel(label); } return this; } /** * Sets the x and y labels if they haven't already been set. * * @param xlabel the label for the x-axis * @param ylabel the label for the y-axis * @return a reference to this {@code Plot} instance */ private Plot setLabelsIfBlank(String xlabel, String ylabel) { if (chart.getPlot() instanceof XYPlot) { XYPlot plot = chart.getXYPlot(); if (plot.getDomainAxis().getLabel().isEmpty()) { plot.getDomainAxis().setLabel(xlabel); } if (plot.getRangeAxis().getLabel().isEmpty()) { plot.getRangeAxis().setLabel(ylabel); } } else if (chart.getPlot() instanceof CategoryPlot) { CategoryPlot plot = chart.getCategoryPlot(); if (plot.getDomainAxis().getLabel().isEmpty()) { plot.getDomainAxis().setLabel(xlabel); } if (plot.getRangeAxis().getLabel().isEmpty()) { plot.getRangeAxis().setLabel(ylabel); } } return this; } /** * Sets the background paint. * * @param paint the background paint * @return a reference to this {@code Plot} instance */ public Plot setBackgroundPaint(Paint paint) { if (chart.getPlot() instanceof XYPlot) { chart.getXYPlot().setBackgroundPaint(paint); } else if (chart.getPlot() instanceof CategoryPlot) { chart.getCategoryPlot().setBackgroundPaint(paint); } return this; } /** * Sets the grid line paint. * * @param paint the grid line paint * @return a reference to this {@code Plot} instance */ public Plot setGridPaint(Paint paint) { if (chart.getPlot() instanceof XYPlot) { chart.getXYPlot().setRangeGridlinePaint(paint); chart.getXYPlot().setDomainGridlinePaint(paint); } else if (chart.getPlot() instanceof CategoryPlot) { chart.getCategoryPlot().setRangeGridlinePaint(paint); chart.getCategoryPlot().setDomainGridlinePaint(paint); } return this; } /** * Sets the x-axis limits. * * @param min the minimum bound for the x-axis * @param max the maximum bound for the x-axis * @return a reference to this {@code Plot} instance */ public Plot setXLim(double min, double max) { if (chart.getPlot() instanceof XYPlot) { chart.getXYPlot().getDomainAxis().setRange(min, max); } return this; } /** * Sets the y-axis limits. * * @param min the minimum bound for the y-axis * @param max the maximum bound for the y-axis * @return a reference to this {@code Plot} instance */ public Plot setYLim(double min, double max) { if (chart.getPlot() instanceof XYPlot) { chart.getXYPlot().getRangeAxis().setRange(min, max); } else if (chart.getPlot() instanceof CategoryPlot) { chart.getCategoryPlot().getRangeAxis().setRange(min, max); } return this; } /** * Creates a new scatter plot series. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot scatter(String label, double[] x, double[] y) { return scatter(label, toList(x), toList(y)); } /** * Creates a new scatter plot series. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot scatter(String label, List<? extends Number> x, List<? extends Number> y) { return scatter(label, x, y, null); } /** * Creates a new scatter plot series. The series is added to the given * dataset, or if {@code null} a new dataset is created. * * @param label the label for the series * @param x the x values * @param y the y values * @param dataset the dataset, or {@code null} if a new dataset should be * created * @return a reference to this {@code Plot} instance */ private Plot scatter(String label, List<? extends Number> x, List<? extends Number> y, XYSeriesCollection dataset) { if (dataset == null) { createXYPlot(); currentDataset++; dataset = new XYSeriesCollection(); } // generate the dataset XYSeries series = new XYSeries(label, false, true); for (int i = 0; i < x.size(); i++) { series.add(x.get(i), y.get(i)); } dataset.addSeries(series); // add the dataset to the plot XYPlot plot = chart.getXYPlot(); plot.setDataset(currentDataset, dataset); // setup the renderer Paint paint = paintHelper.get(dataset.getSeriesKey(0)); XYDotRenderer renderer = new XYDotRenderer(); renderer.setDotHeight(6); renderer.setDotWidth(6); renderer.setBasePaint(paint); renderer.setBaseFillPaint(paint); plot.setRenderer(currentDataset, renderer); return this; } /** * Converts a double array to a list. * * @param x the double array * @return the list of doubles */ private List<Double> toList(double[] x) { List<Double> result = new ArrayList<Double>(); for (int i = 0; i < x.length; i++) { result.add(x[i]); } return result; } /** * Displays the solutions in the given population in a 2D scatter plot. * Only two objectives will be displayed. * * @param label the label for the series * @param population the population * @return a reference to this {@code Plot} instance */ public Plot add(String label, Population population) { Solution solution = population.get(0); if (solution.getNumberOfObjectives() == 1) { return add(label, population, 0, 0); } else { return add(label, population, 0, 1); } } /** * Displays the solutions in the given population in a 2D scatter plot. * The two given objectives will be displayed. * * @param label the label for the series * @param population the population * @param x the objective to plot on the x-axis * @param y the objective to plot on the y-axis * @return a reference to this {@code Plot} instance */ public Plot add(String label, Population population, int x, int y) { List<Number> xs = new ArrayList<Number>(); List<Number> ys = new ArrayList<Number>(); for (Solution solution : population) { if (!solution.violatesConstraints()) { xs.add(solution.getObjective(x)); ys.add(solution.getObjective(y)); } } scatter(label, xs, ys); setLabelsIfBlank("Objective " + (x + 1), "Objective " + (y + 1)); return this; } /** * Displays the runtime data stored in an {@link Accumulator} as one or * more line plots. * * @param accumulator the {@code Accumulator} instance * @return a reference to this {@code Plot} instance */ public Plot add(Accumulator accumulator) { createXYPlot(); currentDataset++; XYSeriesCollection dataset = new XYSeriesCollection(); for (String key : accumulator.keySet()) { if (!key.equals("NFE")) { add(key, accumulator, key, dataset); } } return this; } /** * Displays the runtime data for the given metric as a line plot. * * @param label the label for the series * @param accumulator the {@code Accumulator} instance * @param metric the name of the performance metric to plot * @return a reference to this {@code Plot} instance */ public Plot add(String label, Accumulator accumulator, String metric) { return add(label, accumulator, metric, null); } /** * Displays the runtime data for the given metric as a line plot. The * series is added to the given dataset, or if {@code null} a new dataset * is created. * * @param label the label for the series * @param accumulator the {@code Accumulator} instance * @param metric the name of the performance metric to plot * @param dataset the dataset, or {@code null} if a new dataset should be * created * @return a reference to this {@code Plot} instance */ private Plot add(String label, Accumulator accumulator, String metric, XYSeriesCollection dataset) { List<Number> xs = new ArrayList<Number>(); List<Number> ys = new ArrayList<Number>(); try { for (int i = 0; i < accumulator.size("NFE"); i++) { xs.add((Number) accumulator.get("NFE", i)); ys.add((Number) accumulator.get(metric, i)); } } catch (ClassCastException e) { return this; } line(label, xs, ys, dataset); setLabelsIfBlank("NFE", "Value"); return this; } /** * Creates a new line plot series. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot line(String label, double[] x, double[] y) { return line(label, toList(x), toList(y)); } /** * Creates a new line plot series. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot line(String label, List<? extends Number> x, List<? extends Number> y) { return line(label, x, y, null); } /** * Creates a new line plot series. The series is added to the given * dataset, or if {@code null} a new dataset is created. * * @param label the label for the series * @param x the x values * @param y the y values * @param dataset the dataset, or {@code null} if a new dataset should be * created * @return a reference to this {@code Plot} instance */ private Plot line(String label, List<? extends Number> x, List<? extends Number> y, XYSeriesCollection dataset) { if (dataset == null) { createXYPlot(); currentDataset++; dataset = new XYSeriesCollection(); } // generate the dataset XYSeries series = new XYSeries(label, false, true); for (int i = 0; i < x.size(); i++) { series.add(x.get(i), y.get(i)); } dataset.addSeries(series); // add the dataset to the plot XYPlot plot = chart.getXYPlot(); plot.setDataset(currentDataset, dataset); // setup the renderer Paint paint = paintHelper.get(dataset.getSeriesKey(0)); XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false); renderer.setAutoPopulateSeriesStroke(false); renderer.setBaseStroke(new BasicStroke(3f, 1, 1)); renderer.setBasePaint(paint); renderer.setBaseFillPaint(paint); plot.setRenderer(currentDataset, renderer); return this; } /** * Creates a new area plot series. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot area(String label, double[] x, double[] y) { return area(label, toList(x), toList(y)); } /** * Creates a new area plot series. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot area(String label, List<? extends Number> x, List<? extends Number> y) { return area(label, x, y, null); } /** * Creates a new area plot series. The series is added to the given * dataset, or if {@code null} a new dataset is created. * * @param label the label for the series * @param x the x values * @param y the y values * @param dataset the dataset, or {@code null} if a new dataset should be * created * @return a reference to this {@code Plot} instance */ private Plot area(String label, List<? extends Number> x, List<? extends Number> y, XYSeriesCollection dataset) { if (dataset == null) { createXYPlot(); currentDataset++; dataset = new XYSeriesCollection(); } // generate the dataset XYSeries series = new XYSeries(label, false, true); for (int i = 0; i < x.size(); i++) { series.add(x.get(i), y.get(i)); } dataset.addSeries(series); // add the dataset to the plot XYPlot plot = chart.getXYPlot(); plot.setDataset(currentDataset, dataset); // setup the renderer Paint paint = paintHelper.get(dataset.getSeriesKey(0)); XYAreaRenderer renderer = new XYAreaRenderer(); renderer.setAutoPopulateSeriesStroke(false); renderer.setBaseStroke(new BasicStroke(3f, 1, 1)); renderer.setBasePaint(paint); renderer.setBaseFillPaint(paint); plot.setRenderer(currentDataset, renderer); return this; } /** * Creates a new stacked area plot series. The data will be stacked with * any preceding calls to {@code stacked}. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot stacked(String label, double[] x, double[] y) { return stacked(label, toList(x), toList(y)); } /** * Creates a new stacked area plot series. The data will be stacked with * any preceding calls to {@code stacked}. * * @param label the label for the series * @param x the x values * @param y the y values * @return a reference to this {@code Plot} instance */ public Plot stacked(String label, List<? extends Number> x, List<? extends Number> y) { return stacked(label, x, y, null); } /** * Creates a new area plot series. The data will be stacked with * any preceding calls to {@code stacked}. The series is added to the given * dataset, or if {@code null} a new dataset is created. * * @param label the label for the series * @param x the x values * @param y the y values * @param dataset the dataset, or {@code null} if a new dataset should be * created * @return a reference to this {@code Plot} instance */ private Plot stacked(String label, List<? extends Number> x, List<? extends Number> y, DefaultTableXYDataset dataset) { if (dataset == null) { createXYPlot(); XYPlot plot = chart.getXYPlot(); if (plot.getDataset(currentDataset) instanceof DefaultTableXYDataset) { dataset = (DefaultTableXYDataset) plot.getDataset(currentDataset); } else { currentDataset++; dataset = new DefaultTableXYDataset(); } } // generate the dataset XYSeries series = new XYSeries(label, true, false); for (int i = 0; i < x.size(); i++) { series.add(x.get(i), y.get(i)); } dataset.addSeries(series); // add the dataset to the plot XYPlot plot = chart.getXYPlot(); plot.setDataset(currentDataset, dataset); // setup the renderer Paint paint = paintHelper.get(dataset.getSeriesKey(0)); StackedXYAreaRenderer renderer = new StackedXYAreaRenderer(); renderer.setAutoPopulateSeriesStroke(false); renderer.setBaseStroke(new BasicStroke(3f, 1, 1)); renderer.setBasePaint(paint); renderer.setBaseFillPaint(paint); plot.setRenderer(currentDataset, renderer); return this; } /** * Displays the statistical results from an {@link Analyzer} as a * box-and-whisker plot. * * @param analyzer the {@code Analyzer} instance * @return a reference to this {@code Plot} instance */ public Plot add(Analyzer analyzer) { return add(analyzer.getAnalysis()); } /** * Displays the statistical results from an {@link AnalyzerResults} as a * box-and-whisker plot. * * @param result the {@code AnalyzerResults} instance * @return a reference to this {@code Plot} instance */ public Plot add(AnalyzerResults result) { createCategoryPlot(); DefaultBoxAndWhiskerCategoryDataset dataset = new DefaultBoxAndWhiskerCategoryDataset(); for (String algorithm : result.getAlgorithms()) { for (String indicator : result.get(algorithm).getIndicators()) { List<Double> values = new ArrayList<Double>(); for (double value : result.get(algorithm).get(indicator).getValues()) { values.add(value); } dataset.add(values, algorithm, indicator); } } CategoryPlot plot = chart.getCategoryPlot(); plot.setDataset(dataset); return this; } /** * Modifies the line thickness or point size in the last dataset. The * size is applied to all series in the dataset. * * @param size the size * @return a reference to this {@code Plot} instance */ public Plot withSize(float size) { if (chart.getPlot() instanceof XYPlot) { XYPlot plot = chart.getXYPlot(); XYItemRenderer renderer = plot.getRenderer(currentDataset); if (renderer instanceof XYDotRenderer) { ((XYDotRenderer) renderer).setDotWidth((int) (size * 2)); ((XYDotRenderer) renderer).setDotHeight((int) (size * 2)); } else if (renderer.getBaseStroke() instanceof BasicStroke) { BasicStroke oldStroke = (BasicStroke) renderer.getBaseStroke(); BasicStroke newStroke = new BasicStroke(size, oldStroke.getEndCap(), oldStroke.getLineJoin(), oldStroke.getMiterLimit(), oldStroke.getDashArray(), oldStroke.getDashPhase()); renderer.setBaseStroke(newStroke); } else { renderer.setBaseStroke(new BasicStroke(size, 1, 1)); } } return this; } /** * Modifies the paint (e.g,. color) of each series in the last dataset. * If the dataset contains more series than the number of arguments, the * arguments are reused as needed. * * @param paint one or more paint instances * @return a reference to this {@code Plot} instance */ public Plot withPaint(Paint... paint) { if (chart.getPlot() instanceof XYPlot) { XYPlot plot = chart.getXYPlot(); XYDataset dataset = plot.getDataset(currentDataset); XYItemRenderer renderer = plot.getRenderer(currentDataset); for (int i = 0; i < dataset.getSeriesCount(); i++) { Paint p = paint[i % paint.length]; paintHelper.set(dataset.getSeriesKey(i), p); renderer.setSeriesPaint(i, p); if (renderer instanceof XYLineAndShapeRenderer) { ((XYLineAndShapeRenderer) renderer).setSeriesFillPaint(i, p); } } } else if (chart.getPlot() instanceof CategoryPlot) { CategoryPlot plot = chart.getCategoryPlot(); CategoryDataset dataset = plot.getDataset(); CategoryItemRenderer renderer = plot.getRenderer(); for (int i = 0; i < dataset.getRowCount(); i++) { Paint p = paint[i % paint.length]; paintHelper.set(dataset.getRowKey(i), p); renderer.setSeriesPaint(i, p); } } return this; } /** * Saves the plot to an image file. The type of image is determined from * the filename extension. See {@link #save(File, String, int, int)} for * a list of supported types. * * @param filename the filename * @return a reference to this {@code Plot} instance * @throws IOException if an I/O error occurred */ public Plot save(String filename) throws IOException { return save(new File(filename)); } /** * Saves the plot to an image file. The type of image is determined from * the filename extension. See {@link #save(File, String, int, int)} for * a list of supported types. * * @param file the file * @return a reference to this {@code Plot} instance * @throws IOException if an I/O error occurred */ public Plot save(File file) throws IOException { String filename = file.getName(); String extension = filename.substring(filename.lastIndexOf('.') + 1, filename.length()); return save(file, extension, 800, 600); } /** * Saves the plot to an image file. The format must be one of {@code png}, * {@code jpeg}, or {@code svg} (requires JFreeSVG). * * @param file the file * @param format the image format * @param width the image width * @param height the image height * @return a reference to this {@code Plot} instance * @throws IOException if an I/O error occurred */ public Plot save(File file, String format, int width, int height) throws IOException { if (format.equalsIgnoreCase("PNG")) { ChartUtilities.saveChartAsPNG(file, chart, width, height); } else if (format.equalsIgnoreCase("JPG") || format.equalsIgnoreCase("JPEG")) { ChartUtilities.saveChartAsJPEG(file, chart, width, height); } else if (format.equalsIgnoreCase("SVG")) { String svg = generateSVG(width, height); BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(file)); writer.write( "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); writer.write(svg); writer.write("\n"); writer.flush(); } finally { if (writer != null) { writer.close(); } } } return this; } /** * Generates a string containing a rendering of the chart in SVG format. * This feature is only supported if the JFreeSVG library is included on * the classpath. * * This is copied from JFreeChart's ChartPanel class (version 1.0.19). * * @return A string containing an SVG element for the current chart, or * <code>null</code> if there is a problem with the method invocation * by reflection. */ private String generateSVG(int width, int height) { Graphics2D g2 = createSVGGraphics2D(width, height); if (g2 == null) { throw new IllegalStateException("JFreeSVG library is not present."); } // we suppress shadow generation, because SVG is a vector format and // the shadow effect is applied via bitmap effects... g2.setRenderingHint(new RenderingHints.Key(0) { @Override public boolean isCompatibleValue(Object val) { return val instanceof Boolean; } }, true); String svg = null; Rectangle2D drawArea = new Rectangle2D.Double(0, 0, width, height); chart.draw(g2, drawArea); try { Method m = g2.getClass().getMethod("getSVGElement"); svg = (String) m.invoke(g2); } catch (NoSuchMethodException e) { // null will be returned } catch (SecurityException e) { // null will be returned } catch (IllegalAccessException e) { // null will be returned } catch (IllegalArgumentException e) { // null will be returned } catch (InvocationTargetException e) { // null will be returned } return svg; } /** * This is copied from JFreeChart's ChartPanel class (version 1.0.19). */ private Graphics2D createSVGGraphics2D(int w, int h) { try { Class<?> svgGraphics2d = Class.forName("org.jfree.graphics2d.svg.SVGGraphics2D"); Constructor<?> ctor = svgGraphics2d.getConstructor(int.class, int.class); return (Graphics2D) ctor.newInstance(w, h); } catch (ClassNotFoundException ex) { return null; } catch (NoSuchMethodException ex) { return null; } catch (SecurityException ex) { return null; } catch (InstantiationException ex) { return null; } catch (IllegalAccessException ex) { return null; } catch (IllegalArgumentException ex) { return null; } catch (InvocationTargetException ex) { return null; } } /** * Returns the internal chart. Allows further modification of the * appearance of the chart. * * @return the internal JFreeChart instance */ public JFreeChart getChart() { return chart; } /** * Returns the chart embedded in a Swing panel for display. * * @return a Swing panel for displaying the chart */ public ChartPanel getChartPanel() { return new ChartPanel(chart); } /** * Displays the chart in a standalone window. */ public JFrame show() { return show(800, 600); } /** * Displays the chart in a standalone window. * * @param width the width of the chart * @param height the height of the chart * @return the window that was created */ public JFrame show(int width, int height) { JFrame frame = new JFrame(); frame.getContentPane().setLayout(new BorderLayout()); frame.getContentPane().add(getChartPanel(), BorderLayout.CENTER); frame.setPreferredSize(new Dimension(width, height)); frame.pack(); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setTitle("MOEA Framework Plot"); frame.setVisible(true); return frame; } /** * Displays the chart in a blocking JDialog. */ public JDialog showDialog() { return showDialog(800, 600); } /** * Displays the chart in a blocking JDialog. * * @param width the width of the chart * @param height the height of the chart * @return the window that was created */ public JDialog showDialog(int width, int height) { JDialog frame = new JDialog(); frame.getContentPane().setLayout(new BorderLayout()); frame.getContentPane().add(getChartPanel(), BorderLayout.CENTER); frame.setPreferredSize(new Dimension(width, height)); frame.pack(); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setTitle("MOEA Framework Plot"); frame.setModalityType(ModalityType.APPLICATION_MODAL); frame.setVisible(true); return frame; } }