001/* 
002 * Copyright 2015 Alexander Nozik.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package hep.dataforge.plots.jfreechart;
017
018import hep.dataforge.meta.Meta;
019import hep.dataforge.data.DataPoint;
020import hep.dataforge.data.XYDataAdapter;
021import hep.dataforge.description.MetaReader;
022import hep.dataforge.description.DescriptorBuilder;
023import hep.dataforge.description.Parameter;
024import hep.dataforge.exceptions.NameNotFoundException;
025import hep.dataforge.plots.PlotUtils;
026import hep.dataforge.plots.Plottable;
027import hep.dataforge.plots.XYPlotFrame;
028import hep.dataforge.plots.XYPlottable;
029import java.awt.BasicStroke;
030import java.awt.BorderLayout;
031import java.awt.Color;
032import java.awt.Container;
033import java.awt.Dimension;
034import java.io.IOException;
035import java.io.ObjectInputStream;
036import java.io.OutputStream;
037import java.io.Serializable;
038import java.text.DecimalFormat;
039import java.util.ArrayList;
040import java.util.List;
041import java.util.function.Consumer;
042import javafx.embed.swing.SwingNode;
043import javafx.scene.layout.AnchorPane;
044import javax.swing.JFrame;
045import javax.swing.JPanel;
046import javax.swing.SwingUtilities;
047import javax.swing.WindowConstants;
048import org.jfree.chart.ChartPanel;
049import org.jfree.chart.JFreeChart;
050import org.jfree.chart.axis.DateAxis;
051import org.jfree.chart.axis.LogAxis;
052import org.jfree.chart.axis.NumberAxis;
053import org.jfree.chart.axis.ValueAxis;
054import org.jfree.chart.encoders.SunPNGEncoderAdapter;
055import org.jfree.chart.plot.XYPlot;
056import org.jfree.chart.renderer.xy.XYErrorRenderer;
057import org.jfree.chart.renderer.xy.XYItemRenderer;
058import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
059import org.jfree.data.xy.XYDataset;
060import org.jfree.data.xy.XYIntervalSeries;
061import org.jfree.data.xy.XYIntervalSeriesCollection;
062import org.slf4j.LoggerFactory;
063
064/**
065 *
066 * @author Alexander Nozik
067 */
068public class JFreeChartFrame extends XYPlotFrame implements Serializable {
069
070    /**
071     * Десериализует график из файла и прицепляет его к новой формочке
072     *
073     * @param stream
074     * @return
075     * @throws IOException
076     * @throws ClassNotFoundException
077     */
078    public static JFrame deserialize(ObjectInputStream stream) throws IOException, ClassNotFoundException {
079        JFreeChart jfc = (JFreeChart) stream.readObject();
080
081        JFrame frame = new JFrame("DataForge visualisator");
082        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
083        SwingUtilities.invokeLater(() -> {
084            JPanel panel = new JPanel(new BorderLayout());
085            panel.setPreferredSize(new Dimension(800, 600));
086            frame.setContentPane(panel);
087
088            panel.removeAll();
089            panel.add(new ChartPanel(jfc));
090            panel.revalidate();
091            panel.repaint();
092
093            frame.pack();
094            frame.setVisible(true);
095        });
096        return frame;
097    }
098
099    /**
100     * Index mapping names to datasets
101     */
102    List<String> index = new ArrayList<>();
103
104    private JFreeChart chart;
105    private XYPlot plot;
106
107    /**
108     * Draw new JFrame containing this plot
109     *
110     * @param name
111     * @param annotation
112     * @return
113     */
114    public static JFreeChartFrame drawFrame(String name, Meta annotation) {
115        JFrame frame = new JFrame(name);
116        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
117        JPanel panel = new JPanel(new BorderLayout());
118        panel.setPreferredSize(new Dimension(800, 600));
119        frame.setContentPane(panel);
120
121        SwingUtilities.invokeLater(() -> {
122            frame.pack();
123            frame.setVisible(true);
124        });
125        return new JFreeChartFrame(name, annotation, panel);
126    }
127
128    public JFreeChartFrame(String name, Meta annotation, Container panel) {
129        this(name, annotation, (JFreeChart t) -> {
130            panel.removeAll();
131            panel.add(new ChartPanel(t));
132            panel.revalidate();
133            panel.repaint();
134        });
135    }
136
137    /**
138     * Отрисовка компонента при помощи SwingNode
139     *
140     * @param name
141     * @param annotation
142     * @param parent
143     */
144    public JFreeChartFrame(String name, Meta annotation, AnchorPane parent) {
145        this(name, annotation, (JFreeChart t) -> {
146//            ChartViewer viewer = new ChartViewer(t, true);
147            SwingNode viewer = new SwingNode();
148            JPanel panel = new JPanel(new BorderLayout(), true);
149            panel.removeAll();
150            panel.add(new ChartPanel(t));
151            panel.revalidate();
152            panel.repaint();
153            viewer.setContent(panel);
154
155            AnchorPane.setBottomAnchor(viewer, 0d);
156            AnchorPane.setTopAnchor(viewer, 0d);
157            AnchorPane.setLeftAnchor(viewer, 0d);
158            AnchorPane.setRightAnchor(viewer, 0d);
159            parent.getChildren().add(viewer);
160
161        });
162    }
163
164    private JFreeChartFrame(String name, Meta annotation, Consumer<JFreeChart> container) {
165        super(name, annotation);
166        ValueAxis xaxis = getNumberAxis(annotation.getMeta("xAxis", Meta.buildEmpty("xAxis")));
167        ValueAxis yaxis = getNumberAxis(annotation.getMeta("yAxis", Meta.buildEmpty("YAxis")));
168        plot = new XYPlot(new XYIntervalSeriesCollection(), xaxis, yaxis, new XYErrorRenderer());
169
170        String title = meta().getString("frameTitle", name);
171
172        if (title.equals("default")) {
173            title = "";
174        }
175
176        chart = new JFreeChart(title, plot);
177        applyConfig(annotation);
178        container.accept(chart);
179    }
180
181    protected void attachTo(Consumer<JFreeChart> consumer) {
182        consumer.accept(chart);
183    }
184
185    public JFreeChart getChart() {
186        return chart;
187    }
188
189    private ValueAxis getNumberAxis(Meta annotation) {
190        NumberAxis axis = new NumberAxis();
191        axis.setAutoRangeIncludesZero(false);
192        axis.setAutoRangeStickyZero(false);
193        return axis;
194    }
195
196    private DateAxis getDateAxis(Meta annotation) {
197        DateAxis axis = new DateAxis();
198        return axis;
199    }
200
201    private ValueAxis getLogAxis(Meta annotation) {
202        LogAxis logAxis = new LogAxis();
203        logAxis.setAutoRange(true);
204        logAxis.setAutoTickUnitSelection(false);
205        logAxis.setNumberFormatOverride(new DecimalFormat("0E0"));
206        logAxis.setSmallestValue(1e-20);
207        return logAxis;
208    }
209
210    @Override
211    protected synchronized void updateAxis(String axisName, Meta annotation) {
212        SwingUtilities.invokeLater(() -> {
213            ValueAxis axis;
214            switch (axisName) {
215                case "x":
216                    if (annotation.getBoolean("logScale", false)) {
217                        plot.setDomainAxis(getLogAxis(annotation));
218                    } else if (annotation.getBoolean("timeAxis", false)) {
219                        plot.setDomainAxis(getDateAxis(annotation));
220                    }
221                    axis = plot.getDomainAxis();
222                    break;
223                case "y":
224
225                    if (annotation.getBoolean("logScale", false)) {
226                        plot.setRangeAxis(getLogAxis(annotation));
227                    }
228                    axis = plot.getRangeAxis();
229                    break;
230                default:
231                    throw new NameNotFoundException(axisName, "No such axis in this plot");
232            }
233
234            if (annotation.hasValue("axisTitle")) {
235                String label = annotation.getString("axisTitle");
236                if (annotation.hasValue("axisUnits")) {
237                    label += " (" + annotation.getString("axisUnits") + ")";
238                }
239                axis.setLabel(label);
240            }
241        });
242    }
243
244    @Override
245    protected synchronized void updateFrame(Meta annotation) {
246//        plot.getRenderer().setLegendItemLabelGenerator((XYDataset dataset, int series) -> {
247//            Plottable p = get(dataset.getSeriesKey(series).toString());
248//            return p.getConfig().getString("title", p.getName());
249//        });
250    }
251
252    @Override
253    protected void updatePlotData(String name) {
254        XYPlottable plottable = get(name);
255
256        XYIntervalSeries ser = new XYIntervalSeries(plottable.getName());
257        XYDataAdapter adapter = plottable.adapter();
258        for (DataPoint point : plottable.plotData()) {
259            double x = adapter.getX(point).doubleValue();
260            double y = adapter.getY(point).doubleValue();
261            double xErr = adapter.getXerr(point).doubleValue();
262            double yErr = adapter.getYerr(point).doubleValue();
263            ser.add(x, x - xErr, x + xErr, y, y - yErr, y + yErr);
264        }
265
266        final XYIntervalSeriesCollection data = new XYIntervalSeriesCollection();
267        data.addSeries(ser);
268
269        if (!index.contains(plottable.getName())) {
270            index.add(plottable.getName());
271        }
272
273        SwingUtilities.invokeLater(() -> {
274            plot.setDataset(index.indexOf(name), data);
275        });
276    }
277
278    @Override
279    protected void updatePlotConfig(String name) {
280        final Plottable plottable = get(name);
281        if (!index.contains(plottable.getName())) {
282            index.add(plottable.getName());
283        }
284        int num = index.indexOf(name);
285
286        SwingUtilities.invokeLater(() -> {
287
288            MetaReader reader = new MetaReader(DescriptorBuilder.buildFrom(plottable.getClass()), plottable.getConfig(), meta());
289
290            XYItemRenderer render;
291            if (reader.getBoolean("showErrors", false)) {
292                render = new XYErrorRenderer();
293            } else {
294                boolean showLines = reader.getBoolean("showLine", false);
295                boolean showSymbols = reader.getBoolean("showSymbol", true);
296                render = new XYLineAndShapeRenderer(showLines, showSymbols);
297            }
298
299            plot.setRenderer(num, render);
300
301            render.setLegendItemLabelGenerator((XYDataset dataset, int series) -> {
302                Plottable p = get(dataset.getSeriesKey(series).toString());
303                return p.getConfig().getString("title", p.getName());
304            });
305            double thickness = PlotUtils.getThickness(reader);
306            if (thickness > 0) {
307                render.setSeriesStroke(num, new BasicStroke((float) thickness));
308            }
309
310            Color color = PlotUtils.getColor(reader);
311            if (color != null) {
312                render.setSeriesPaint(num, color);
313            }
314        });
315
316    }
317
318    /**
319     * Take a snapshot of plot frame and save it in a given OutputStream
320     *
321     * @param stream
322     * @param cfg
323     */
324    @Parameter(name = "width", def = "800", description = "The width of the snapshot in pixels")
325    @Parameter(name = "height", def = "600", description = "The height of the snapshot in pixels")
326    public synchronized void toPNG(OutputStream stream, Meta cfg) {
327        SwingUtilities.invokeLater(() -> {
328            try {
329                new SunPNGEncoderAdapter().encode(chart.createBufferedImage(cfg.getInt("width", 800), cfg.getInt("height", 600)), stream);
330            } catch (IOException ex) {
331                LoggerFactory.getLogger(getClass()).error("IO error during image encoding", ex);
332            }
333        });
334    }
335
336}