/* =========================================================== * Orson Charts : a 3D chart library for the Java(tm) platform * =========================================================== * * (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved. * * http://www.object-refinery.com/orsoncharts/index.html * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * If you do not wish to be bound by the terms of the GPL, an alternative * commercial license can be purchased. For details, please see visit the * Orson Charts home page: * * http://www.object-refinery.com/orsoncharts/index.html * */ package com.orsoncharts.fx; import java.io.File; import java.io.IOException; import javafx.scene.control.ContextMenu; import javafx.scene.control.Control; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.Skinnable; import javafx.scene.input.MouseEvent; import javafx.stage.FileChooser; import javafx.stage.WindowEvent; import com.orsoncharts.Chart3D; import com.orsoncharts.graphics3d.ExportUtils; import com.orsoncharts.graphics3d.RenderedElement; import com.orsoncharts.graphics3d.RenderingInfo; import com.orsoncharts.graphics3d.ViewPoint3D; import com.orsoncharts.interaction.fx.FXChart3DMouseEvent; import com.orsoncharts.util.ArgChecks; import com.orsoncharts.util.ExportFormats; /** * A control for displaying a {@link Chart3D} in JavaFX. This control embeds * a {@link Chart3DCanvas} and also provides a context menu. * * @since 1.4 */ public class Chart3DViewer extends Control implements Skinnable { /** The chart to display. */ private Chart3D chart; /** * The chart canvas (which is a child node for this control). This * reference is kept for convenience, and is initialised by the control's * skin. */ private Chart3DCanvas canvas; /** The context menu that will be attached to the canvas. */ private ContextMenu contextMenu; /** * The zoom multiplier (applicable for the zoom in and out options in * the context menu). */ private double zoomMultiplier = 0.95; /** * Creates a new viewer to display the supplied chart in JavaFX. * * @param chart the chart ({@code null} not permitted). */ public Chart3DViewer(Chart3D chart) { this(chart, true); } /** * Creates a new viewer instance. * * @param chart the chart ({@code null} not permitted). * @param contextMenuEnabled enable the context menu? */ public Chart3DViewer(Chart3D chart, boolean contextMenuEnabled) { ArgChecks.nullNotPermitted(chart, "chart"); this.chart = chart; getStyleClass().add("chart3d-control"); this.contextMenu = createContextMenu(); this.contextMenu.setOnShowing((WindowEvent event) -> { Chart3DViewer viewer = Chart3DViewer.this; if (viewer.canvas != null) { viewer.canvas.setRotateViewEnabled(false); viewer.canvas.setTooltipEnabled(false); } }); this.contextMenu.setOnHiding((WindowEvent event) -> { Chart3DViewer viewer = Chart3DViewer.this; if (viewer.canvas != null) { viewer.canvas.setRotateViewEnabled(true); viewer.canvas.setTooltipEnabled(true); } }); setContextMenu(this.contextMenu); } /** * Returns the chart that is being displayed by this node. * * @return The chart (never {@code null}). */ public Chart3D getChart() { return this.chart; } /** * Sets the chart to be displayed by this node. * * @param chart the chart ({@code null} not permitted). */ public void setChart(Chart3D chart) { ArgChecks.nullNotPermitted(chart, "chart"); this.chart = chart; this.canvas.setChart(chart); } /** * Returns the canvas used within this control to display the chart. * * @return The canvas (never {@code null}). */ public Chart3DCanvas getCanvas() { return this.canvas; } /** * Sets the canvas used within this control to display the chart. * This method is called by the control's skin, you should not need to * call it directly. * * @param canvas the canvas ({@code null} not permitted). */ public void setCanvas(final Chart3DCanvas canvas) { ArgChecks.nullNotPermitted(canvas, "canvas"); this.canvas = canvas; this.canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> { RenderingInfo info = canvas.getRenderingInfo(); RenderedElement element = info.findElementAt( event.getX(), event.getY()); Chart3DViewer viewer = Chart3DViewer.this; viewer.fireEvent(new FXChart3DMouseEvent(viewer, FXChart3DMouseEvent.MOUSE_CLICKED, element, event)); }); } /** * Returns the multiplier used for the zoom in and out options in the * context menu. The default value is {@code 0.95}. * * @return The zoom multiplier. */ public double getZoomMultiplier() { return this.zoomMultiplier; } /** * Sets the zoom multiplier used for the zoom in and out options in the * context menu. When zooming in, the current viewing distance will be * multiplied by this value (which defaults to 0.95). When zooming out, * the viewing distance is multiplied by 1 / zoomMultiplier. * * @param multiplier the new multiplier. */ public void setZoomMultiplier(double multiplier) { this.zoomMultiplier = multiplier; } /** * Creates the context menu. * * @return The context menu. */ private ContextMenu createContextMenu() { final ContextMenu menu = new ContextMenu(); MenuItem zoomIn = new MenuItem("Zoom In"); zoomIn.setOnAction(e -> { handleZoom(this.zoomMultiplier); }); MenuItem zoomOut = new MenuItem("Zoom Out"); zoomOut.setOnAction(e -> { handleZoom(1.0 / this.zoomMultiplier); }); MenuItem zoomToFit = new MenuItem("Zoom To Fit"); zoomToFit.setOnAction(e -> { handleZoomToFit(); }); SeparatorMenuItem separator = new SeparatorMenuItem(); Menu export = new Menu("Export As"); MenuItem pngItem = new MenuItem("PNG..."); pngItem.setOnAction(e -> { handleExportToPNG(); }); export.getItems().add(pngItem); MenuItem jpegItem = new MenuItem("JPEG..."); jpegItem.setOnAction(e -> { handleExportToJPEG(); }); export.getItems().add(jpegItem); // automatically detect if OrsonPDF is on the classpath and, if it is, // provide a PDF export menu item if (ExportFormats.isOrsonPDFAvailable()) { MenuItem pdfItem = new MenuItem("PDF..."); pdfItem.setOnAction(e -> { handleExportToPDF(); }); export.getItems().add(pdfItem); } // automatically detect if JFreeSVG is on the classpath and, if it is, // provide a SVG export menu item if (ExportFormats.isJFreeSVGAvailable()) { MenuItem svgItem = new MenuItem("SVG..."); svgItem.setOnAction(e -> { handleExportToSVG(); }); export.getItems().add(svgItem); } menu.getItems().addAll(zoomIn, zoomOut, zoomToFit, separator, export); return menu; } /** * A handler for the zoom in and out options in the context menu. * * @param multiplier the multiplier (less than 1.0 zooms in, greater than * 1.0 zooms out). */ private void handleZoom(double multiplier) { ViewPoint3D viewPt = this.chart.getViewPoint(); double minDistance = this.canvas.getMinViewingDistance(); double maxDistance = minDistance * this.canvas.getMaxViewingDistanceMultiplier(); double valRho = Math.max(minDistance, Math.min(maxDistance, viewPt.getRho() * multiplier)); viewPt.setRho(valRho); this.canvas.draw(); } /** * A handler for the zoom to fit option in the context menu. */ private void handleZoomToFit() { this.canvas.zoomToFit(canvas.getWidth(), canvas.getHeight()); } /** * A handler for the export to PDF option in the context menu. Note that * the Export to PDF menu item is only installed if OrsonPDF is on the * classpath. */ private void handleExportToPDF() { FileChooser fileChooser = new FileChooser(); fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( "Portable Document Format (PDF)", "pdf")); fileChooser.setTitle("Export to PDF"); File file = fileChooser.showSaveDialog(this.getScene().getWindow()); if (file != null) { ExportUtils.writeAsPDF(this.chart, (int) getWidth(), (int) getHeight(), file); } } /** * A handler for the export to SVG option in the context menu. */ private void handleExportToSVG() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Export to SVG"); fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( "Scalable Vector Graphics (SVG)", "svg")); File file = fileChooser.showSaveDialog(this.getScene().getWindow()); if (file != null) { ExportUtils.writeAsSVG(this.chart, (int) getWidth(), (int) getHeight(), file); } } /** * A handler for the export to PNG option in the context menu. */ private void handleExportToPNG() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Export to PNG"); fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( "Portable Network Graphics (PNG)", "png")); File file = fileChooser.showSaveDialog(this.getScene().getWindow()); if (file != null) { try { ExportUtils.writeAsPNG(this.chart, (int) getWidth(), (int) getHeight(), file); } catch (IOException ex) { // FIXME: show a dialog with the error } } } /** * A handler for the export to JPEG option in the context menu. */ private void handleExportToJPEG() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Export to JPEG"); fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter( "JPEG", "jpg")); File file = fileChooser.showSaveDialog(this.getScene().getWindow()); if (file != null) { try { ExportUtils.writeAsJPEG(this.chart, (int) getWidth(), (int) getHeight(), file); } catch (IOException ex) { // FIXME: show a dialog with the error } } } @Override public String getUserAgentStylesheet() { return Chart3DViewer.class.getResource("chart3d-viewer.css") .toExternalForm(); } }