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}