001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 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, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes: 036 * -------- 037 * 27-Jun-2014 : Version 1 (DG); 038 * 039 */ 040 041package org.jfree.chart.fx; 042 043import java.io.File; 044import java.io.IOException; 045import java.util.ArrayList; 046import java.util.List; 047import javafx.event.ActionEvent; 048import javafx.scene.control.ContextMenu; 049import javafx.scene.control.Control; 050import javafx.scene.control.Menu; 051import javafx.scene.control.MenuItem; 052import javafx.scene.control.Skinnable; 053import javafx.stage.FileChooser; 054import javafx.stage.WindowEvent; 055import org.jfree.chart.ChartMouseEvent; 056import org.jfree.chart.ChartRenderingInfo; 057import org.jfree.chart.JFreeChart; 058import org.jfree.chart.fx.interaction.ChartMouseEventFX; 059import org.jfree.chart.fx.interaction.ChartMouseListenerFX; 060import org.jfree.chart.util.ExportUtils; 061import org.jfree.chart.util.ParamChecks; 062 063/** 064 * A control for displaying a {@link JFreeChart} in JavaFX (embeds a 065 * {@link ChartCanvas}, adds drag zooming and provides a popup menu for export 066 * to PNG/JPG/SVG and PDF formats). Many behaviours(tooltips, zooming etc) are 067 * provided directly by the canvas. 068 * 069 * <p>THE API FOR THIS CLASS IS SUBJECT TO CHANGE IN FUTURE RELEASES. This is 070 * so that we can incorporate feedback on the (new) JavaFX support in 071 * JFreeChart.</p> 072 * 073 * @since 1.0.18 074 */ 075public class ChartViewer extends Control implements Skinnable, 076 ChartMouseListenerFX { 077 078 /** The chart to display. */ 079 private JFreeChart chart; 080 081 /** The context menu that will be attached to the canvas. */ 082 private ContextMenu contextMenu; 083 084 /** Does the viewer show tooltips from the chart? */ 085 private boolean tooltipEnabled; 086 087 /** Storage for registered chart mouse listeners. */ 088 private transient List<ChartMouseListenerFX> chartMouseListeners; 089 090 /** 091 * Creates a new viewer to display the supplied chart in JavaFX. 092 * 093 * @param chart the chart ({@code null} not permitted). 094 */ 095 public ChartViewer(JFreeChart chart) { 096 this(chart, true); 097 } 098 099 /** 100 * Creates a new viewer instance. 101 * 102 * @param chart the chart ({@code null} not permitted). 103 * @param contextMenuEnabled enable the context menu? 104 */ 105 public ChartViewer(JFreeChart chart, boolean contextMenuEnabled) { 106 ParamChecks.nullNotPermitted(chart, "chart"); 107 this.chart = chart; 108 getStyleClass().add("chart-control"); 109 this.contextMenu = createContextMenu(); 110 this.contextMenu.setOnShowing((WindowEvent event) -> { 111 ChartViewer.this.setTooltipEnabled(false); 112 }); 113 this.contextMenu.setOnHiding((WindowEvent event) -> { 114 ChartViewer.this.setTooltipEnabled(true); 115 }); 116 setContextMenu(this.contextMenu); 117 this.tooltipEnabled = true; 118 this.chartMouseListeners = new ArrayList<ChartMouseListenerFX>(); 119 } 120 121 @Override 122 protected String getUserAgentStylesheet() { 123 return ChartViewer.class.getResource("chart-viewer.css") 124 .toExternalForm(); 125 } 126 127 /** 128 * Returns the chart that is being displayed by this node. 129 * 130 * @return The chart (never {@code null}). 131 */ 132 public JFreeChart getChart() { 133 return this.chart; 134 } 135 136 /** 137 * Sets the chart to be displayed by this node. 138 * 139 * @param chart the chart ({@code null} not permitted). 140 */ 141 public void setChart(JFreeChart chart) { 142 ParamChecks.nullNotPermitted(chart, "chart"); 143 this.chart = chart; 144 ChartViewerSkin skin = (ChartViewerSkin) getSkin(); 145 skin.setChart(chart); 146 } 147 148 /** 149 * Returns the flag that controls whether or not tooltips are displayed 150 * for the chart. 151 * 152 * @return A boolean. 153 */ 154 public boolean isTooltipEnabled() { 155 return this.tooltipEnabled; 156 } 157 158 /** 159 * Sets the flag that controls whether or not the chart tooltips are shown 160 * by this viewer. 161 * 162 * @param enabled the new flag value. 163 */ 164 public void setTooltipEnabled(boolean enabled) { 165 this.tooltipEnabled = enabled; 166 ChartViewerSkin skin = (ChartViewerSkin) getSkin(); 167 if (skin != null) { 168 skin.setTooltipEnabled(enabled); 169 } 170 } 171 172 /** 173 * Returns the rendering info from the most recent drawing of the chart. 174 * 175 * @return The rendering info (possibly {@code null}). 176 */ 177 public ChartRenderingInfo getRenderingInfo() { 178 ChartViewerSkin skin = (ChartViewerSkin) getSkin(); 179 if (skin != null) { 180 return skin.getRenderingInfo(); 181 } 182 return null; 183 } 184 185 /** 186 * Hides the zoom rectangle. The work is delegated to the control's 187 * current skin. 188 */ 189 public void hideZoomRectangle() { 190 ChartViewerSkin skin = (ChartViewerSkin) getSkin(); 191 skin.setZoomRectangleVisible(false); 192 } 193 194 /** 195 * Sets the size and location of the zoom rectangle and makes it visible 196 * if it wasn't already visible. The work is delegated to the control's 197 * current skin. 198 * 199 * @param x the x-location. 200 * @param y the y-location. 201 * @param w the width. 202 * @param h the height. 203 */ 204 public void showZoomRectangle(double x, double y, double w, double h) { 205 ChartViewerSkin skin = (ChartViewerSkin) getSkin(); 206 skin.showZoomRectangle(x, y, w, h); 207 } 208 209 /** 210 * Registers a listener to receive {@link ChartMouseEvent} notifications 211 * from this viewer. 212 * 213 * @param listener the listener ({@code null} not permitted). 214 */ 215 public void addChartMouseListener(ChartMouseListenerFX listener) { 216 ParamChecks.nullNotPermitted(listener, "listener"); 217 this.chartMouseListeners.add(listener); 218 } 219 220 /** 221 * Removes a listener from the list of objects listening for chart mouse 222 * events. 223 * 224 * @param listener the listener. 225 */ 226 public void removeChartMouseListener(ChartMouseListenerFX listener) { 227 ParamChecks.nullNotPermitted(listener, "listener"); 228 this.chartMouseListeners.remove(listener); 229 } 230 231 /** 232 * Creates the context menu. 233 * 234 * @return The context menu. 235 */ 236 private ContextMenu createContextMenu() { 237 final ContextMenu menu = new ContextMenu(); 238 239 Menu export = new Menu("Export As"); 240 241 MenuItem pngItem = new MenuItem("PNG..."); 242 pngItem.setOnAction((ActionEvent e) -> { handleExportToPNG(); }); 243 export.getItems().add(pngItem); 244 245 MenuItem jpegItem = new MenuItem("JPEG..."); 246 jpegItem.setOnAction((ActionEvent e) -> { handleExportToJPEG(); }); 247 export.getItems().add(jpegItem); 248 249 if (ExportUtils.isOrsonPDFAvailable()) { 250 MenuItem pdfItem = new MenuItem("PDF..."); 251 pdfItem.setOnAction((ActionEvent e) -> { 252 handleExportToPDF(); 253 }); 254 export.getItems().add(pdfItem); 255 } 256 if (ExportUtils.isJFreeSVGAvailable()) { 257 MenuItem svgItem = new MenuItem("SVG..."); 258 svgItem.setOnAction((ActionEvent e) -> { 259 handleExportToSVG(); 260 }); 261 export.getItems().add(svgItem); 262 } 263 menu.getItems().add(export); 264 return menu; 265 } 266 267 /** 268 * A handler for the export to PDF option in the context menu. 269 */ 270 private void handleExportToPDF() { 271 FileChooser fileChooser = new FileChooser(); 272 fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( 273 "Portable Document Format (PDF)", "pdf")); 274 fileChooser.setTitle("Export to PDF"); 275 File file = fileChooser.showSaveDialog(this.getScene().getWindow()); 276 if (file != null) { 277 ExportUtils.writeAsPDF(this.chart, (int) getWidth(), 278 (int) getHeight(), file); 279 } 280 } 281 282 /** 283 * A handler for the export to SVG option in the context menu. 284 */ 285 private void handleExportToSVG() { 286 FileChooser fileChooser = new FileChooser(); 287 fileChooser.setTitle("Export to SVG"); 288 fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( 289 "Scalable Vector Graphics (SVG)", "svg")); 290 File file = fileChooser.showSaveDialog(this.getScene().getWindow()); 291 if (file != null) { 292 ExportUtils.writeAsSVG(this.chart, (int) getWidth(), 293 (int) getHeight(), file); 294 } 295 } 296 297 /** 298 * A handler for the export to PNG option in the context menu. 299 */ 300 private void handleExportToPNG() { 301 FileChooser fileChooser = new FileChooser(); 302 fileChooser.setTitle("Export to PNG"); 303 fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( 304 "Portable Network Graphics (PNG)", "png")); 305 File file = fileChooser.showSaveDialog(this.getScene().getWindow()); 306 if (file != null) { 307 try { 308 ExportUtils.writeAsPNG(this.chart, (int) getWidth(), 309 (int) getHeight(), file); 310 } catch (IOException ex) { 311 // FIXME: show a dialog with the error 312 } 313 } 314 } 315 316 /** 317 * A handler for the export to JPEG option in the context menu. 318 */ 319 private void handleExportToJPEG() { 320 FileChooser fileChooser = new FileChooser(); 321 fileChooser.setTitle("Export to JPEG"); 322 fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( 323 "JPEG", "jpg")); 324 File file = fileChooser.showSaveDialog(this.getScene().getWindow()); 325 if (file != null) { 326 try { 327 ExportUtils.writeAsJPEG(this.chart, (int) getWidth(), 328 (int) getHeight(), file); 329 } catch (IOException ex) { 330 // FIXME: show a dialog with the error 331 } 332 } 333 } 334 335 @Override 336 public void chartMouseClicked(ChartMouseEventFX event) { 337 // relay the event from the canvas to our registered listeners 338 for (ChartMouseListenerFX listener: this.chartMouseListeners) { 339 listener.chartMouseClicked(event); 340 } 341 } 342 343 @Override 344 public void chartMouseMoved(ChartMouseEventFX event) { 345 // relay the event from the canvas to our registered listeners 346 for (ChartMouseListenerFX listener: this.chartMouseListeners) { 347 listener.chartMouseMoved(event); 348 } 349 } 350 351} 352