001/* ================================================ 002 * JFreeChart-FX : JavaFX extensions for JFreeChart 003 * ================================================ 004 * 005 * (C) Copyright 2017-present, by David Gilbert and Contributors. 006 * 007 * Project Info: https://github.com/jfree/jfreechart-fx 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ---------------- 028 * ChartViewer.java 029 * ---------------- 030 * (C) Copyright 2014-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.chart.fx; 038 039import java.io.File; 040import java.io.IOException; 041import javafx.scene.control.ContextMenu; 042import javafx.scene.control.Menu; 043import javafx.scene.control.MenuItem; 044import javafx.scene.input.ContextMenuEvent; 045import javafx.scene.layout.Region; 046import javafx.scene.paint.Color; 047import javafx.scene.paint.Paint; 048import javafx.scene.shape.Rectangle; 049import javafx.stage.FileChooser; 050import org.jfree.chart.ChartMouseEvent; 051import org.jfree.chart.ChartRenderingInfo; 052import org.jfree.chart.JFreeChart; 053import org.jfree.chart.fx.interaction.ChartMouseListenerFX; 054import org.jfree.chart.fx.interaction.ZoomHandlerFX; 055import org.jfree.chart.util.ExportUtils; 056import org.jfree.chart.util.Args; 057 058/** 059 * A control for displaying a {@link JFreeChart} in JavaFX (embeds a 060 * {@link ChartCanvas}, adds drag zooming and provides a popup menu for export 061 * to PNG/JPG/SVG and PDF formats). Many behaviours (tooltips, zooming etc) are 062 * provided directly by the canvas. 063 */ 064public class ChartViewer extends Region { 065 066 private ChartCanvas canvas; 067 068 /** 069 * The zoom rectangle is used to display the zooming region when 070 * doing a drag-zoom with the mouse. Most of the time this rectangle 071 * is not visible. 072 */ 073 private Rectangle zoomRectangle; 074 075 /** The context menu for the chart viewer. */ 076 private ContextMenu contextMenu; 077 078 /** 079 * Creates a new instance, initially with no chart to display. This 080 * constructor is required so that this control can be used within 081 * FXML. 082 */ 083 public ChartViewer() { 084 this(null); 085 } 086 087 /** 088 * Creates a new viewer to display the supplied chart in JavaFX. 089 * 090 * @param chart the chart ({@code null} permitted). 091 */ 092 public ChartViewer(JFreeChart chart) { 093 this(chart, true); 094 } 095 096 /** 097 * Creates a new viewer instance. 098 * 099 * @param chart the chart ({@code null} permitted). 100 * @param contextMenuEnabled enable the context menu? 101 */ 102 public ChartViewer(JFreeChart chart, boolean contextMenuEnabled) { 103 this.canvas = new ChartCanvas(chart); 104 this.canvas.setTooltipEnabled(true); 105 this.canvas.addMouseHandler(new ZoomHandlerFX("zoom", this)); 106 setFocusTraversable(true); 107 getChildren().add(this.canvas); 108 109 this.zoomRectangle = new Rectangle(0, 0, new Color(0, 0, 1, 0.25)); 110 this.zoomRectangle.setManaged(false); 111 this.zoomRectangle.setVisible(false); 112 getChildren().add(this.zoomRectangle); 113 114 this.contextMenu = createContextMenu(); 115 setOnContextMenuRequested((ContextMenuEvent event) -> contextMenu.show(ChartViewer.this.getScene().getWindow(), 116 event.getScreenX(), event.getScreenY())); 117 this.contextMenu.setOnShowing( 118 e -> ChartViewer.this.getCanvas().setTooltipEnabled(false)); 119 this.contextMenu.setOnHiding( 120 e -> ChartViewer.this.getCanvas().setTooltipEnabled(true)); 121 } 122 123 /** 124 * Returns the chart that is being displayed by this viewer. 125 * 126 * @return The chart (possibly {@code null}). 127 */ 128 public JFreeChart getChart() { 129 return this.canvas.getChart(); 130 } 131 132 /** 133 * Sets the chart to be displayed by this viewer. 134 * 135 * @param chart the chart ({@code null} not permitted). 136 */ 137 public void setChart(JFreeChart chart) { 138 Args.nullNotPermitted(chart, "chart"); 139 this.canvas.setChart(chart); 140 } 141 142 /** 143 * Returns the {@link ChartCanvas} embedded in this component. 144 * 145 * @return The {@code ChartCanvas} (never {@code null}). 146 */ 147 public ChartCanvas getCanvas() { 148 return this.canvas; 149 } 150 151 /** 152 * Returns the context menu for this component. 153 * 154 * @return The context menu for this component. 155 */ 156 public ContextMenu getContextMenu() { 157 return this.contextMenu; 158 } 159 160 /** 161 * Returns the rendering info from the most recent drawing of the chart. 162 * 163 * @return The rendering info (possibly {@code null}). 164 */ 165 public ChartRenderingInfo getRenderingInfo() { 166 return getCanvas().getRenderingInfo(); 167 } 168 169 /** 170 * Returns the current fill paint for the zoom rectangle. 171 * 172 * @return The fill paint. 173 */ 174 public Paint getZoomFillPaint() { 175 return this.zoomRectangle.getFill(); 176 } 177 178 /** 179 * Sets the fill paint for the zoom rectangle. 180 * 181 * @param paint the new paint. 182 */ 183 public void setZoomFillPaint(Paint paint) { 184 this.zoomRectangle.setFill(paint); 185 } 186 187 @Override 188 protected void layoutChildren() { 189 super.layoutChildren(); 190 this.canvas.setLayoutX(0); 191 this.canvas.setLayoutY(0); 192 this.canvas.setWidth(getWidth()); 193 this.canvas.setHeight(getHeight()); 194 } 195 196 /** 197 * Registers a listener to receive {@link ChartMouseEvent} notifications 198 * from the canvas embedded in this viewer. 199 * 200 * @param listener the listener ({@code null} not permitted). 201 */ 202 public void addChartMouseListener(ChartMouseListenerFX listener) { 203 Args.nullNotPermitted(listener, "listener"); 204 this.canvas.addChartMouseListener(listener); 205 } 206 207 /** 208 * Removes a listener from the list of objects listening for chart mouse 209 * events. 210 * 211 * @param listener the listener. 212 */ 213 public void removeChartMouseListener(ChartMouseListenerFX listener) { 214 Args.nullNotPermitted(listener, "listener"); 215 this.canvas.removeChartMouseListener(listener); 216 } 217 218 /** 219 * Creates the context menu. 220 * 221 * @return The context menu. 222 */ 223 private ContextMenu createContextMenu() { 224 final ContextMenu menu = new ContextMenu(); 225 menu.setAutoHide(true); 226 Menu export = new Menu("Export As"); 227 228 MenuItem pngItem = new MenuItem("PNG..."); 229 pngItem.setOnAction(e -> handleExportToPNG()); 230 export.getItems().add(pngItem); 231 232 MenuItem jpegItem = new MenuItem("JPEG..."); 233 jpegItem.setOnAction(e -> handleExportToJPEG()); 234 export.getItems().add(jpegItem); 235 236 if (ExportUtils.isOrsonPDFAvailable()) { 237 MenuItem pdfItem = new MenuItem("PDF..."); 238 pdfItem.setOnAction(e -> handleExportToPDF()); 239 export.getItems().add(pdfItem); 240 } 241 if (ExportUtils.isJFreeSVGAvailable()) { 242 MenuItem svgItem = new MenuItem("SVG..."); 243 svgItem.setOnAction(e -> handleExportToSVG()); 244 export.getItems().add(svgItem); 245 } 246 menu.getItems().add(export); 247 return menu; 248 } 249 250 /** 251 * A handler for the export to PDF option in the context menu. 252 */ 253 private void handleExportToPDF() { 254 FileChooser chooser = new FileChooser(); 255 chooser.setTitle("Export to PDF"); 256 FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter( 257 "Portable Document Format (PDF)", "*.pdf"); 258 chooser.getExtensionFilters().add(filter); 259 File file = chooser.showSaveDialog(getScene().getWindow()); 260 if (file != null) { 261 ExportUtils.writeAsPDF(this.canvas.getChart(), (int) getWidth(), 262 (int) getHeight(), file); 263 } 264 } 265 266 /** 267 * A handler for the export to SVG option in the context menu. 268 */ 269 private void handleExportToSVG() { 270 FileChooser chooser = new FileChooser(); 271 chooser.setTitle("Export to SVG"); 272 FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter( 273 "Scalable Vector Graphics (SVG)", "*.svg"); 274 chooser.getExtensionFilters().add(filter); 275 File file = chooser.showSaveDialog(getScene().getWindow()); 276 if (file != null) { 277 ExportUtils.writeAsSVG(this.canvas.getChart(), (int) getWidth(), 278 (int) getHeight(), file); 279 } 280 } 281 282 /** 283 * A handler for the export to PNG option in the context menu. 284 */ 285 private void handleExportToPNG() { 286 FileChooser chooser = new FileChooser(); 287 chooser.setTitle("Export to PNG"); 288 FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter( 289 "Portable Network Graphics (PNG)", "*.png"); 290 chooser.getExtensionFilters().add(filter); 291 File file = chooser.showSaveDialog(getScene().getWindow()); 292 if (file != null) { 293 try { 294 ExportUtils.writeAsPNG(this.canvas.getChart(), (int) getWidth(), 295 (int) getHeight(), file); 296 } catch (IOException ex) { 297 // FIXME: show a dialog with the error 298 throw new RuntimeException(ex); 299 } 300 } 301 } 302 303 /** 304 * A handler for the export to JPEG option in the context menu. 305 */ 306 private void handleExportToJPEG() { 307 FileChooser chooser = new FileChooser(); 308 chooser.setTitle("Export to JPEG"); 309 FileChooser.ExtensionFilter filter = new FileChooser.ExtensionFilter("JPEG", "*.jpg"); 310 chooser.getExtensionFilters().add(filter); 311 File file = chooser.showSaveDialog(getScene().getWindow()); 312 if (file != null) { 313 try { 314 ExportUtils.writeAsJPEG(this.canvas.getChart(), (int) getWidth(), 315 (int) getHeight(), file); 316 } catch (IOException ex) { 317 // FIXME: show a dialog with the error 318 throw new RuntimeException(ex); 319 } 320 } 321 } 322 323 /** 324 * Sets the size and location of the zoom rectangle and makes it visible 325 * if it wasn't already visible.. This method is provided for the use of 326 * the {@link ZoomHandlerFX} class, you won't normally need to call it 327 * directly. 328 * 329 * @param x the x-location. 330 * @param y the y-location. 331 * @param w the width. 332 * @param h the height. 333 */ 334 public void showZoomRectangle(double x, double y, double w, double h) { 335 this.zoomRectangle.setX(x); 336 this.zoomRectangle.setY(y); 337 this.zoomRectangle.setWidth(w); 338 this.zoomRectangle.setHeight(h); 339 this.zoomRectangle.setVisible(true); 340 } 341 342 /** 343 * Hides the zoom rectangle. This method is provided for the use of the 344 * {@link ZoomHandlerFX} class, you won't normally need to call it directly. 345 */ 346 public void hideZoomRectangle() { 347 this.zoomRectangle.setVisible(false); 348 } 349 350} 351