/* =========================================================== * JFreeChart : a free chart library for the Java(tm) platform * =========================================================== * * (C) Copyright 2000-2014, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jfreechart/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * --------------- * ChartPanel.java * --------------- * (C) Copyright 2000-2014, by Object Refinery Limited and Contributors. * * Original Author: David Gilbert (for Object Refinery Limited); * Contributor(s): Andrzej Porebski; * Soren Caspersen; * Jonathan Nash; * Hans-Jurgen Greiner; * Andreas Schneider; * Daniel van Enckevort; * David M O'Donnell; * Arnaud Lelievre; * Matthias Rose; * Onno vd Akker; * Sergei Ivanov; * Ulrich Voigt - patch 2686040; * Alessandro Borges - patch 1460845; * Martin Hoeller; * Michael Zinsmaier; * Simon Legner - patch from bug 1129; * * Changes (from 28-Jun-2001) * -------------------------- * 28-Jun-2001 : Integrated buffering code contributed by S???ren * Caspersen (DG); * 18-Sep-2001 : Updated header and fixed DOS encoding problem (DG); * 22-Nov-2001 : Added scaling to improve display of charts in small sizes (DG); * 26-Nov-2001 : Added property editing, saving and printing (DG); * 11-Dec-2001 : Transferred saveChartAsPNG method to new ChartUtilities * class (DG); * 13-Dec-2001 : Added tooltips (DG); * 16-Jan-2002 : Added an optional crosshair, based on the implementation by * Jonathan Nash. Renamed the tooltips class (DG); * 23-Jan-2002 : Implemented zooming based on code by Hans-Jurgen Greiner (DG); * 05-Feb-2002 : Improved tooltips setup. Renamed method attemptSaveAs() * --> doSaveAs() and made it public rather than private (DG); * 28-Mar-2002 : Added a new constructor (DG); * 09-Apr-2002 : Changed initialisation of tooltip generation, as suggested by * Hans-Jurgen Greiner (DG); * 27-May-2002 : New interactive zooming methods based on code by Hans-Jurgen * Greiner. Renamed JFreeChartPanel --> ChartPanel, moved * constants to ChartPanelConstants interface (DG); * 31-May-2002 : Fixed a bug with interactive zooming and added a way to * control if the zoom rectangle is filled in or drawn as an * outline. A mouse drag gesture towards the top left now causes * an autoRangeBoth() and is a way to undo zooms (AS); * 11-Jun-2002 : Reinstated handleClick method call in mouseClicked() to get * crosshairs working again (DG); * 13-Jun-2002 : Added check for null popup menu in mouseDragged method (DG); * 18-Jun-2002 : Added get/set methods for minimum and maximum chart * dimensions (DG); * 25-Jun-2002 : Removed redundant code (DG); * 27-Aug-2002 : Added get/set methods for popup menu (DG); * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); * 22-Oct-2002 : Added translation methods for screen <--> Java2D, contributed * by Daniel van Enckevort (DG); * 05-Nov-2002 : Added a chart reference to the ChartMouseEvent class (DG); * 22-Nov-2002 : Added test in zoom method for inverted axes, supplied by * David M O'Donnell (DG); * 14-Jan-2003 : Implemented ChartProgressListener interface (DG); * 14-Feb-2003 : Removed deprecated setGenerateTooltips method (DG); * 12-Mar-2003 : Added option to enforce filename extension (see bug id * 643173) (DG); * 08-Sep-2003 : Added internationalization via use of properties * resourceBundle (RFE 690236) (AL); * 18-Sep-2003 : Added getScaleX() and getScaleY() methods (protected) as * requested by Irv Thomae (DG); * 12-Nov-2003 : Added zooming support for the FastScatterPlot class (DG); * 24-Nov-2003 : Minor Javadoc updates (DG); * 04-Dec-2003 : Added anchor point for crosshair calculation (DG); * 17-Jan-2004 : Added new methods to set tooltip delays to be used in this * chart panel. Refer to patch 877565 (MR); * 02-Feb-2004 : Fixed bug in zooming trigger and added zoomTriggerDistance * attribute (DG); * 08-Apr-2004 : Changed getScaleX() and getScaleY() from protected to * public (DG); * 15-Apr-2004 : Added zoomOutFactor and zoomInFactor (DG); * 21-Apr-2004 : Fixed zooming bug in mouseReleased() method (DG); * 13-Jul-2004 : Added check for null chart (DG); * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG); * 11-Nov-2004 : Moved constants back in from ChartPanelConstants (DG); * 12-Nov-2004 : Modified zooming mechanism to support zooming within * subplots (DG); * 26-Jan-2005 : Fixed mouse zooming for horizontal category plots (DG); * 11-Apr-2005 : Added getFillZoomRectangle() method, renamed * setHorizontalZoom() --> setDomainZoomable(), * setVerticalZoom() --> setRangeZoomable(), added * isDomainZoomable() and isRangeZoomable(), added * getHorizontalAxisTrace() and getVerticalAxisTrace(), * renamed autoRangeBoth() --> restoreAutoBounds(), * autoRangeHorizontal() --> restoreAutoDomainBounds(), * autoRangeVertical() --> restoreAutoRangeBounds() (DG); * 12-Apr-2005 : Removed working areas, added getAnchorPoint() method, * added protected accessors for tracelines (DG); * 18-Apr-2005 : Made constants final (DG); * 26-Apr-2005 : Removed LOGGER (DG); * 01-Jun-2005 : Fixed zooming for combined plots - see bug report * 1212039, fix thanks to Onno vd Akker (DG); * 25-Nov-2005 : Reworked event listener mechanism (DG); * ------------- JFREECHART 1.0.x --------------------------------------------- * 01-Aug-2006 : Fixed minor bug in restoreAutoRangeBounds() (DG); * 04-Sep-2006 : Renamed attemptEditChartProperties() --> * doEditChartProperties() and made public (DG); * 13-Sep-2006 : Don't generate ChartMouseEvents if the panel's chart is null * (fixes bug 1556951) (DG); * 05-Mar-2007 : Applied patch 1672561 by Sergei Ivanov, to fix zoom rectangle * drawing for dynamic charts (DG); * 17-Apr-2007 : Fix NullPointerExceptions in zooming for combined plots (DG); * 24-May-2007 : When the look-and-feel changes, update the popup menu if there * is one (DG); * 06-Jun-2007 : Fixed coordinates for drawing buffer image (DG); * 24-Sep-2007 : Added zoomAroundAnchor flag, and handle clearing of chart * buffer (DG); * 25-Oct-2007 : Added default directory attribute (DG); * 07-Nov-2007 : Fixed (rare) bug in refreshing off-screen image (DG); * 07-May-2008 : Fixed bug in zooming that triggered zoom for a rectangle * outside of the data area (DG); * 08-May-2008 : Fixed serialization bug (DG); * 15-Aug-2008 : Increased default maxDrawWidth/Height (DG); * 18-Sep-2008 : Modified creation of chart buffer (DG); * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by * Jess Thrysoee (DG); * 13-Jan-2009 : Fixed zooming methods to trigger only one plot * change event (DG); * 16-Jan-2009 : Use XOR for zoom rectangle only if useBuffer is false (DG); * 18-Mar-2009 : Added mouse wheel support (DG); * 19-Mar-2009 : Added panning on mouse drag support - based on Ulrich * Voigt's patch 2686040 (DG); * 26-Mar-2009 : Changed fillZoomRectangle default to true, and only change * cursor for CTRL-mouse-click if panning is enabled (DG); * 01-Apr-2009 : Fixed panning, and added different mouse event mask for * MacOSX (DG); * 08-Apr-2009 : Added copy to clipboard support, based on patch 1460845 * by Alessandro Borges (DG); * 09-Apr-2009 : Added overlay support (DG); * 10-Apr-2009 : Set chartBuffer background to match ChartPanel (DG); * 05-May-2009 : Match scaling (and insets) in doCopy() (DG); * 01-Jun-2009 : Check for null chart in mousePressed() method (DG); * 08-Jun-2009 : Fixed bug in setMouseWheelEnabled() (DG); * 06-Jul-2009 : Clear off-screen buffer to fully transparent (DG); * 10-Oct-2011 : localization fix: bug #3353913 (MH); * 15-Jun-2012 : Removed JCommon dependencies (DG); * 29-Aug-2014 : Localisation updates from patch attached to bug 1129 (SL); * */ package org.jfree.chart; import java.awt.AWTEvent; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Insets; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.Toolkit; import java.awt.Transparency; import java.awt.datatransfer.Clipboard; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.EventListener; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import javax.swing.JFileChooser; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.event.EventListenerList; import javax.swing.filechooser.FileNameExtensionFilter; import org.jfree.chart.editor.ChartEditor; import org.jfree.chart.editor.ChartEditorManager; import org.jfree.chart.entity.ChartEntity; import org.jfree.chart.entity.EntityCollection; import org.jfree.chart.event.ChartChangeEvent; import org.jfree.chart.event.ChartChangeListener; import org.jfree.chart.event.ChartProgressEvent; import org.jfree.chart.event.ChartProgressListener; import org.jfree.chart.event.OverlayChangeEvent; import org.jfree.chart.event.OverlayChangeListener; import org.jfree.chart.panel.AbstractMouseHandler; import org.jfree.chart.panel.Overlay; import org.jfree.chart.panel.PanHandler; import org.jfree.chart.panel.ZoomHandler; import org.jfree.chart.panel.selectionhandler.SelectionManager; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.Zoomable; import org.jfree.chart.util.ParamChecks; import org.jfree.chart.util.ResourceBundleWrapper; import org.jfree.chart.util.SerialUtils; /** * A Swing GUI component for displaying a {@link JFreeChart} object. * <P> * The panel registers with the chart to receive notification of changes to any * component of the chart. The chart is redrawn automatically whenever this * notification is received. */ public class ChartPanel extends JPanel implements ChartChangeListener, ChartProgressListener, ActionListener, MouseListener, MouseMotionListener, OverlayChangeListener, Printable, Serializable { /** For serialization. */ private static final long serialVersionUID = 6046366297214274674L; /** * Default setting for buffer usage. The default has been changed to * <code>true</code> from version 1.0.13 onwards, because of a severe * performance problem with drawing the zoom rectangle using XOR (which * now happens only when the buffer is NOT used). */ public static final boolean DEFAULT_BUFFER_USED = true; /** The default panel width. */ public static final int DEFAULT_WIDTH = 680; /** The default panel height. */ public static final int DEFAULT_HEIGHT = 420; /** The default limit below which chart scaling kicks in. */ public static final int DEFAULT_MINIMUM_DRAW_WIDTH = 300; /** The default limit below which chart scaling kicks in. */ public static final int DEFAULT_MINIMUM_DRAW_HEIGHT = 200; /** The default limit above which chart scaling kicks in. */ public static final int DEFAULT_MAXIMUM_DRAW_WIDTH = 1024; /** The default limit above which chart scaling kicks in. */ public static final int DEFAULT_MAXIMUM_DRAW_HEIGHT = 768; /** The minimum size required to perform a zoom on a rectangle */ public static final int DEFAULT_ZOOM_TRIGGER_DISTANCE = 10; /** Properties action command. */ public static final String PROPERTIES_COMMAND = "PROPERTIES"; /** * Copy action command. * * @since 1.0.13 */ public static final String COPY_COMMAND = "COPY"; /** Save action command. */ public static final String SAVE_COMMAND = "SAVE"; /** Action command to save as PNG. */ private static final String SAVE_AS_PNG_COMMAND = "SAVE_AS_PNG"; /** Action command to save as SVG. */ private static final String SAVE_AS_SVG_COMMAND = "SAVE_AS_SVG"; /** Action command to save as PDF. */ private static final String SAVE_AS_PDF_COMMAND = "SAVE_AS_PDF"; /** Print action command. */ public static final String PRINT_COMMAND = "PRINT"; /** Zoom in (both axes) action command. */ public static final String ZOOM_IN_BOTH_COMMAND = "ZOOM_IN_BOTH"; /** Zoom in (domain axis only) action command. */ public static final String ZOOM_IN_DOMAIN_COMMAND = "ZOOM_IN_DOMAIN"; /** Zoom in (range axis only) action command. */ public static final String ZOOM_IN_RANGE_COMMAND = "ZOOM_IN_RANGE"; /** Zoom out (both axes) action command. */ public static final String ZOOM_OUT_BOTH_COMMAND = "ZOOM_OUT_BOTH"; /** Zoom out (domain axis only) action command. */ public static final String ZOOM_OUT_DOMAIN_COMMAND = "ZOOM_DOMAIN_BOTH"; /** Zoom out (range axis only) action command. */ public static final String ZOOM_OUT_RANGE_COMMAND = "ZOOM_RANGE_BOTH"; /** Zoom reset (both axes) action command. */ public static final String ZOOM_RESET_BOTH_COMMAND = "ZOOM_RESET_BOTH"; /** Zoom reset (domain axis only) action command. */ public static final String ZOOM_RESET_DOMAIN_COMMAND = "ZOOM_RESET_DOMAIN"; /** Zoom reset (range axis only) action command. */ public static final String ZOOM_RESET_RANGE_COMMAND = "ZOOM_RESET_RANGE"; /** The chart that is displayed in the panel. */ private JFreeChart chart; /** Storage for registered (chart) mouse listeners. */ private transient EventListenerList chartMouseListeners; /** A flag that controls whether or not the off-screen buffer is used. */ private boolean useBuffer; /** A flag that indicates that the buffer should be refreshed. */ private boolean refreshBuffer; /** A buffer for the rendered chart. */ private transient Image chartBuffer; /** The height of the chart buffer. */ private int chartBufferHeight; /** The width of the chart buffer. */ private int chartBufferWidth; /** * The minimum width for drawing a chart (uses scaling for smaller widths). */ private int minimumDrawWidth; /** * The minimum height for drawing a chart (uses scaling for smaller * heights). */ private int minimumDrawHeight; /** * The maximum width for drawing a chart (uses scaling for bigger * widths). */ private int maximumDrawWidth; /** * The maximum height for drawing a chart (uses scaling for bigger * heights). */ private int maximumDrawHeight; /** The popup menu for the frame. */ private JPopupMenu popup; /** The drawing info collected the last time the chart was drawn. */ private ChartRenderingInfo info; /** The chart anchor point. */ private Point2D anchor; /** The scale factor used to draw the chart. */ private double scaleX; /** The scale factor used to draw the chart. */ private double scaleY; /** The plot orientation. */ private PlotOrientation orientation = PlotOrientation.VERTICAL; /** A flag that controls whether or not domain zooming is enabled. */ private boolean domainZoomable = false; /** A flag that controls whether or not range zooming is enabled. */ private boolean rangeZoomable = false; /** * The zoom rectangle starting point (selected by the user with a mouse * click). This is a point on the screen, not the chart (which may have * been scaled up or down to fit the panel). */ private Point2D zoomPoint = null; /** The zoom rectangle (selected by the user with the mouse). */ private transient Rectangle2D zoomRectangle = null; /** Controls if the zoom rectangle is drawn as an outline or filled. */ private boolean fillZoomRectangle = true; /** The minimum distance required to drag the mouse to trigger a zoom. */ private int zoomTriggerDistance; /** A vertical trace line. */ private transient Line2D verticalTraceLine; /** A horizontal trace line. */ private transient Line2D horizontalTraceLine; /** Menu item for zooming in on a chart (both axes). */ private JMenuItem zoomInBothMenuItem; /** Menu item for zooming in on a chart (domain axis). */ private JMenuItem zoomInDomainMenuItem; /** Menu item for zooming in on a chart (range axis). */ private JMenuItem zoomInRangeMenuItem; /** Menu item for zooming out on a chart. */ private JMenuItem zoomOutBothMenuItem; /** Menu item for zooming out on a chart (domain axis). */ private JMenuItem zoomOutDomainMenuItem; /** Menu item for zooming out on a chart (range axis). */ private JMenuItem zoomOutRangeMenuItem; /** Menu item for resetting the zoom (both axes). */ private JMenuItem zoomResetBothMenuItem; /** Menu item for resetting the zoom (domain axis only). */ private JMenuItem zoomResetDomainMenuItem; /** Menu item for resetting the zoom (range axis only). */ private JMenuItem zoomResetRangeMenuItem; /** * The default directory for saving charts to file. * * @since 1.0.7 */ private File defaultDirectoryForSaveAs; /** A flag that controls whether or not file extensions are enforced. */ private boolean enforceFileExtensions; /** A flag that indicates if original tooltip settings are changed. */ private boolean toolTipSettingsActive; /** * The tooltip initial delay to be used in this chart panel (defaults to * the global ToolTipManager setting). */ private int toolTipInitialDelay; /** * The tooltip dismiss delay to be used in this chart panel (defaults to * the global ToolTipManager setting). */ private int toolTipDismissDelay; /** * The tooltip reshow delay to be used in this chart panel (defaults to * the global ToolTipManager setting). */ private int toolTipReshowDelay; /** Original initial tooltip delay of ToolTipManager.sharedInstance(). */ private int originalToolTipInitialDelay; /** Original reshow tooltip delay of ToolTipManager.sharedInstance(). */ private int originalToolTipReshowDelay; /** Original dismiss tooltip delay of ToolTipManager.sharedInstance(). */ private int originalToolTipDismissDelay; /** The factor used to zoom in on an axis range. */ private double zoomInFactor = 0.5; /** The factor used to zoom out on an axis range. */ private double zoomOutFactor = 2.0; /** * A flag that controls whether zoom operations are centred on the * current anchor point, or the centre point of the relevant axis. * * @since 1.0.7 */ private boolean zoomAroundAnchor; /** * The paint used to draw the zoom rectangle outline. * * @since 1.0.13 */ private transient Paint zoomOutlinePaint; /** * The zoom fill paint (should use transparency). * * @since 1.0.13 */ private transient Paint zoomFillPaint; /** The resourceBundle for the localization. */ protected static ResourceBundle localizationResources = ResourceBundleWrapper.getBundle( "org.jfree.chart.LocalizationBundle"); /** * A list of overlays for the panel. * * @since 1.0.13 */ private List<Overlay> overlays; //adding mouse handlers to support selection ... private SelectionManager selectionManager; /** * The mouse handlers that are available to deal with mouse events. */ private List<AbstractMouseHandler> availableLiveMouseHandlers; /** * The current "live" mouse handler. One of the handlers from the * 'availableMouseHandlers' list will be selected (typically in the * mousePressed() method) to be the live handler. */ private AbstractMouseHandler liveMouseHandler; /** * A list of auxiliary mouse handlers that will be called after the live * handler has done it's work. */ private List<AbstractMouseHandler> auxiliaryMouseHandlers; /** * The zoom handler that is installed by default. */ private ZoomHandler zoomHandler; /** * The selection shape (may be <code>null</code>). */ private Shape selectionShape; /** * The selection fill paint (may be <code>null</code>). */ private Paint selectionFillPaint; /** * The selection outline paint */ private Paint selectionOutlinePaint = Color.darkGray; /** * The selection outline stroke */ private transient Stroke selectionOutlineStroke = new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 4.0f, new float[] { 3.0f, 3.0f }, 0.0f); /** * Constructs a panel that displays the specified chart. * * @param chart the chart. */ public ChartPanel(JFreeChart chart) { this( chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT, DEFAULT_BUFFER_USED, true, // properties true, // save true, // print true, // zoom true // tooltips ); } /** * Constructs a panel containing a chart. The <code>useBuffer</code> flag * controls whether or not an offscreen <code>BufferedImage</code> is * maintained for the chart. If the buffer is used, more memory is * consumed, but panel repaints will be a lot quicker in cases where the * chart itself hasn't changed (for example, when another frame is moved * to reveal the panel). WARNING: If you set the <code>useBuffer</code> * flag to false, note that the mouse zooming rectangle will (in that case) * be drawn using XOR, and there is a SEVERE performance problem with that * on JRE6 on Windows. * * @param chart the chart. * @param useBuffer a flag controlling whether or not an off-screen buffer * is used (read the warning above before setting this * to <code>false</code>). */ public ChartPanel(JFreeChart chart, boolean useBuffer) { this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT, useBuffer, true, // properties true, // save true, // print true, // zoom true // tooltips ); } /** * Constructs a JFreeChart panel. * * @param chart the chart. * @param properties a flag indicating whether or not the chart property * editor should be available via the popup menu. * @param save a flag indicating whether or not save options should be * available via the popup menu. * @param print a flag indicating whether or not the print option * should be available via the popup menu. * @param zoom a flag indicating whether or not zoom options should * be added to the popup menu. * @param tooltips a flag indicating whether or not tooltips should be * enabled for the chart. */ public ChartPanel(JFreeChart chart, boolean properties, boolean save, boolean print, boolean zoom, boolean tooltips) { this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH, DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH, DEFAULT_MAXIMUM_DRAW_HEIGHT, DEFAULT_BUFFER_USED, properties, save, print, zoom, tooltips ); } /** * Constructs a JFreeChart panel. * * @param chart the chart. * @param width the preferred width of the panel. * @param height the preferred height of the panel. * @param minimumDrawWidth the minimum drawing width. * @param minimumDrawHeight the minimum drawing height. * @param maximumDrawWidth the maximum drawing width. * @param maximumDrawHeight the maximum drawing height. * @param useBuffer a flag that indicates whether to use the off-screen * buffer to improve performance (at the expense of * memory). * @param properties a flag indicating whether or not the chart property * editor should be available via the popup menu. * @param save a flag indicating whether or not save options should be * available via the popup menu. * @param print a flag indicating whether or not the print option * should be available via the popup menu. * @param zoom a flag indicating whether or not zoom options should be * added to the popup menu. * @param tooltips a flag indicating whether or not tooltips should be * enabled for the chart. */ public ChartPanel(JFreeChart chart, int width, int height, int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, boolean properties, boolean save, boolean print, boolean zoom, boolean tooltips) { this(chart, width, height, minimumDrawWidth, minimumDrawHeight, maximumDrawWidth, maximumDrawHeight, useBuffer, properties, true, save, print, zoom, tooltips); } /** * Constructs a JFreeChart panel. * * @param chart the chart. * @param width the preferred width of the panel. * @param height the preferred height of the panel. * @param minimumDrawWidth the minimum drawing width. * @param minimumDrawHeight the minimum drawing height. * @param maximumDrawWidth the maximum drawing width. * @param maximumDrawHeight the maximum drawing height. * @param useBuffer a flag that indicates whether to use the off-screen * buffer to improve performance (at the expense of * memory). * @param properties a flag indicating whether or not the chart property * editor should be available via the popup menu. * @param copy a flag indicating whether or not a copy option should be * available via the popup menu. * @param save a flag indicating whether or not save options should be * available via the popup menu. * @param print a flag indicating whether or not the print option * should be available via the popup menu. * @param zoom a flag indicating whether or not zoom options should be * added to the popup menu. * @param tooltips a flag indicating whether or not tooltips should be * enabled for the chart. * * @since 1.0.13 */ public ChartPanel(JFreeChart chart, int width, int height, int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth, int maximumDrawHeight, boolean useBuffer, boolean properties, boolean copy, boolean save, boolean print, boolean zoom, boolean tooltips) { setChart(chart); this.chartMouseListeners = new EventListenerList(); this.info = new ChartRenderingInfo(); setPreferredSize(new Dimension(width, height)); this.useBuffer = useBuffer; this.refreshBuffer = false; this.minimumDrawWidth = minimumDrawWidth; this.minimumDrawHeight = minimumDrawHeight; this.maximumDrawWidth = maximumDrawWidth; this.maximumDrawHeight = maximumDrawHeight; this.zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE; // set up popup menu... this.popup = null; if (properties || copy || save || print || zoom) { this.popup = createPopupMenu(properties, copy, save, print, zoom); } enableEvents(AWTEvent.MOUSE_EVENT_MASK); enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK); setDisplayToolTips(tooltips); addMouseListener(this); addMouseMotionListener(this); this.defaultDirectoryForSaveAs = null; this.enforceFileExtensions = true; // initialize ChartPanel-specific tool tip delays with // values the from ToolTipManager.sharedInstance() ToolTipManager ttm = ToolTipManager.sharedInstance(); this.toolTipInitialDelay = ttm.getInitialDelay(); this.toolTipDismissDelay = ttm.getDismissDelay(); this.toolTipReshowDelay = ttm.getReshowDelay(); this.zoomAroundAnchor = false; this.zoomOutlinePaint = Color.BLUE; this.zoomFillPaint = new Color(0, 0, 255, 63); this.overlays = new ArrayList<Overlay>(); this.availableLiveMouseHandlers = new ArrayList<AbstractMouseHandler>(); this.zoomHandler = new ZoomHandler(); this.availableLiveMouseHandlers.add(zoomHandler); PanHandler panHandler = new PanHandler(); int panMask = InputEvent.CTRL_MASK; // for MacOSX we can't use the CTRL key for mouse drags, see: // http://developer.apple.com/qa/qa2004/qa1362.html String osName = System.getProperty("os.name").toLowerCase(); if (osName.startsWith("mac os x")) { panMask = InputEvent.ALT_MASK; } panHandler.setModifier(panMask); this.availableLiveMouseHandlers.add(panHandler); this.auxiliaryMouseHandlers = new ArrayList<AbstractMouseHandler>(); } /** * Returns the chart contained in the panel. * * @return The chart (possibly <code>null</code>). */ public JFreeChart getChart() { return this.chart; } /** * Sets the chart that is displayed in the panel. * * @param chart the chart (<code>null</code> permitted). */ public void setChart(JFreeChart chart) { // stop listening for changes to the existing chart if (this.chart != null) { this.chart.removeChangeListener(this); this.chart.removeProgressListener(this); } // add the new chart this.chart = chart; if (chart != null) { this.chart.addChangeListener(this); this.chart.addProgressListener(this); Plot plot = chart.getPlot(); this.domainZoomable = false; this.rangeZoomable = false; if (plot instanceof Zoomable) { Zoomable z = (Zoomable) plot; this.domainZoomable = z.isDomainZoomable(); this.rangeZoomable = z.isRangeZoomable(); this.orientation = z.getOrientation(); } } else { this.domainZoomable = false; this.rangeZoomable = false; } if (this.useBuffer) { this.refreshBuffer = true; } repaint(); } /** * Returns the minimum drawing width for charts. * <P> * If the width available on the panel is less than this, then the chart is * drawn at the minimum width then scaled down to fit. * * @return The minimum drawing width. */ public int getMinimumDrawWidth() { return this.minimumDrawWidth; } /** * Sets the minimum drawing width for the chart on this panel. * <P> * At the time the chart is drawn on the panel, if the available width is * less than this amount, the chart will be drawn using the minimum width * then scaled down to fit the available space. * * @param width The width. */ public void setMinimumDrawWidth(int width) { this.minimumDrawWidth = width; } /** * Returns the maximum drawing width for charts. * <P> * If the width available on the panel is greater than this, then the chart * is drawn at the maximum width then scaled up to fit. * * @return The maximum drawing width. */ public int getMaximumDrawWidth() { return this.maximumDrawWidth; } /** * Sets the maximum drawing width for the chart on this panel. * <P> * At the time the chart is drawn on the panel, if the available width is * greater than this amount, the chart will be drawn using the maximum * width then scaled up to fit the available space. * * @param width The width. */ public void setMaximumDrawWidth(int width) { this.maximumDrawWidth = width; } /** * Returns the minimum drawing height for charts. * <P> * If the height available on the panel is less than this, then the chart * is drawn at the minimum height then scaled down to fit. * * @return The minimum drawing height. */ public int getMinimumDrawHeight() { return this.minimumDrawHeight; } /** * Sets the minimum drawing height for the chart on this panel. * <P> * At the time the chart is drawn on the panel, if the available height is * less than this amount, the chart will be drawn using the minimum height * then scaled down to fit the available space. * * @param height The height. */ public void setMinimumDrawHeight(int height) { this.minimumDrawHeight = height; } /** * Returns the maximum drawing height for charts. * <P> * If the height available on the panel is greater than this, then the * chart is drawn at the maximum height then scaled up to fit. * * @return The maximum drawing height. */ public int getMaximumDrawHeight() { return this.maximumDrawHeight; } /** * Sets the maximum drawing height for the chart on this panel. * <P> * At the time the chart is drawn on the panel, if the available height is * greater than this amount, the chart will be drawn using the maximum * height then scaled up to fit the available space. * * @param height The height. */ public void setMaximumDrawHeight(int height) { this.maximumDrawHeight = height; } /** * Returns the X scale factor for the chart. This will be 1.0 if no * scaling has been used. * * @return The scale factor. */ public double getScaleX() { return this.scaleX; } /** * Returns the Y scale factory for the chart. This will be 1.0 if no * scaling has been used. * * @return The scale factor. */ public double getScaleY() { return this.scaleY; } /** * Returns the anchor point. * * @return The anchor point (possibly <code>null</code>). */ public Point2D getAnchor() { return this.anchor; } /** * Sets the anchor point. This method is provided for the use of * subclasses, not end users. * * @param anchor the anchor point (<code>null</code> permitted). */ protected void setAnchor(Point2D anchor) { this.anchor = anchor; } /** * Returns the popup menu. * * @return The popup menu. */ public JPopupMenu getPopupMenu() { return this.popup; } /** * Sets the popup menu for the panel. * * @param popup the popup menu (<code>null</code> permitted). */ public void setPopupMenu(JPopupMenu popup) { this.popup = popup; } /** * Returns the chart rendering info from the most recent chart redraw. * * @return The chart rendering info. */ public ChartRenderingInfo getChartRenderingInfo() { return this.info; } /** * A convenience method that switches on mouse-based zooming. * * @param flag <code>true</code> enables zooming and rectangle fill on * zoom. */ public void setMouseZoomable(boolean flag) { setMouseZoomable(flag, true); } /** * A convenience method that switches on mouse-based zooming. * * @param flag <code>true</code> if zooming enabled * @param fillRectangle <code>true</code> if zoom rectangle is filled, * false if rectangle is shown as outline only. */ public void setMouseZoomable(boolean flag, boolean fillRectangle) { setDomainZoomable(flag); setRangeZoomable(flag); setFillZoomRectangle(fillRectangle); } /** * Returns the flag that determines whether or not zooming is enabled for * the domain axis. * * @return A boolean. */ public boolean isDomainZoomable() { return this.domainZoomable; } /** * Sets the flag that controls whether or not zooming is enable for the * domain axis. A check is made to ensure that the current plot supports * zooming for the domain values. * * @param flag <code>true</code> enables zooming if possible. */ public void setDomainZoomable(boolean flag) { if (flag) { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { Zoomable z = (Zoomable) plot; this.domainZoomable = z.isDomainZoomable(); } } else { this.domainZoomable = false; } } /** * Returns the flag that determines whether or not zooming is enabled for * the range axis. * * @return A boolean. */ public boolean isRangeZoomable() { return this.rangeZoomable; } /** * A flag that controls mouse-based zooming on the vertical axis. * * @param flag <code>true</code> enables zooming. */ public void setRangeZoomable(boolean flag) { if (flag) { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { Zoomable z = (Zoomable) plot; this.rangeZoomable = z.isRangeZoomable(); } } else { this.rangeZoomable = false; } } /** * Returns the flag that controls whether or not the zoom rectangle is * filled when drawn. * * @return A boolean. */ public boolean getFillZoomRectangle() { return this.fillZoomRectangle; } /** * A flag that controls how the zoom rectangle is drawn. * * @param flag <code>true</code> instructs to fill the rectangle on * zoom, otherwise it will be outlined. */ public void setFillZoomRectangle(boolean flag) { this.fillZoomRectangle = flag; } /** * Returns the zoom trigger distance. This controls how far the mouse must * move before a zoom action is triggered. * * @return The distance (in Java2D units). */ public int getZoomTriggerDistance() { return this.zoomTriggerDistance; } /** * Sets the zoom trigger distance. This controls how far the mouse must * move before a zoom action is triggered. * * @param distance the distance (in Java2D units). */ public void setZoomTriggerDistance(int distance) { this.zoomTriggerDistance = distance; } /** * Returns the default directory for the "save as" option. * * @return The default directory (possibly <code>null</code>). * * @since 1.0.7 */ public File getDefaultDirectoryForSaveAs() { return this.defaultDirectoryForSaveAs; } /** * Sets the default directory for the "save as" option. If you set this * to <code>null</code>, the user's default directory will be used. * * @param directory the directory (<code>null</code> permitted). * * @since 1.0.7 */ public void setDefaultDirectoryForSaveAs(File directory) { if (directory != null) { if (!directory.isDirectory()) { throw new IllegalArgumentException( "The 'directory' argument is not a directory."); } } this.defaultDirectoryForSaveAs = directory; } /** * Returns <code>true</code> if file extensions should be enforced, and * <code>false</code> otherwise. * * @return The flag. * * @see #setEnforceFileExtensions(boolean) */ public boolean isEnforceFileExtensions() { return this.enforceFileExtensions; } /** * Sets a flag that controls whether or not file extensions are enforced. * * @param enforce the new flag value. * * @see #isEnforceFileExtensions() */ public void setEnforceFileExtensions(boolean enforce) { this.enforceFileExtensions = enforce; } /** * Returns the flag that controls whether or not zoom operations are * centered around the current anchor point. * * @return A boolean. * * @since 1.0.7 * * @see #setZoomAroundAnchor(boolean) */ public boolean getZoomAroundAnchor() { return this.zoomAroundAnchor; } /** * Sets the flag that controls whether or not zoom operations are * centered around the current anchor point. * * @param zoomAroundAnchor the new flag value. * * @since 1.0.7 * * @see #getZoomAroundAnchor() */ public void setZoomAroundAnchor(boolean zoomAroundAnchor) { this.zoomAroundAnchor = zoomAroundAnchor; } /** * Returns the zoom rectangle fill paint. * * @return The zoom rectangle fill paint (never <code>null</code>). * * @see #setZoomFillPaint(java.awt.Paint) * @see #setFillZoomRectangle(boolean) * * @since 1.0.13 */ public Paint getZoomFillPaint() { return this.zoomFillPaint; } /** * Sets the zoom rectangle fill paint. * * @param paint the paint (<code>null</code> not permitted). * * @see #getZoomFillPaint() * @see #getFillZoomRectangle() * * @since 1.0.13 */ public void setZoomFillPaint(Paint paint) { ParamChecks.nullNotPermitted(paint, "paint"); this.zoomFillPaint = paint; } /** * Returns the zoom rectangle outline paint. * * @return The zoom rectangle outline paint (never <code>null</code>). * * @see #setZoomOutlinePaint(java.awt.Paint) * @see #setFillZoomRectangle(boolean) * * @since 1.0.13 */ public Paint getZoomOutlinePaint() { return this.zoomOutlinePaint; } /** * Sets the zoom rectangle outline paint. * * @param paint the paint (<code>null</code> not permitted). * * @see #getZoomOutlinePaint() * @see #getFillZoomRectangle() * * @since 1.0.13 */ public void setZoomOutlinePaint(Paint paint) { this.zoomOutlinePaint = paint; } /** * The mouse wheel handler. */ private MouseWheelHandler mouseWheelHandler; /** * Returns <code>true</code> if the mouse wheel handler is enabled, and * <code>false</code> otherwise. * * @return A boolean. * * @since 1.0.13 */ public boolean isMouseWheelEnabled() { return this.mouseWheelHandler != null; } /** * Enables or disables mouse wheel support for the panel. * * @param flag a boolean. * * @since 1.0.13 */ public void setMouseWheelEnabled(boolean flag) { if (flag && this.mouseWheelHandler == null) { this.mouseWheelHandler = new MouseWheelHandler(this); } else if (!flag && this.mouseWheelHandler != null) { this.removeMouseWheelListener(this.mouseWheelHandler); this.mouseWheelHandler = null; } } /** * Add an overlay to the panel. * * @param overlay the overlay (<code>null</code> not permitted). * * @since 1.0.13 */ public void addOverlay(Overlay overlay) { ParamChecks.nullNotPermitted(overlay, "overlay"); this.overlays.add(overlay); overlay.addChangeListener(this); repaint(); } /** * Removes an overlay from the panel. * * @param overlay the overlay to remove (<code>null</code> not permitted). * * @since 1.0.13 */ public void removeOverlay(Overlay overlay) { ParamChecks.nullNotPermitted(overlay, "overlay"); boolean removed = this.overlays.remove(overlay); if (removed) { overlay.removeChangeListener(this); repaint(); } } /** * Handles a change to an overlay by repainting the panel. * * @param event the event. * * @since 1.0.13 */ @Override public void overlayChanged(OverlayChangeEvent event) { repaint(); } /** * Switches the display of tooltips for the panel on or off. Note that * tooltips can only be displayed if the chart has been configured to * generate tooltip items. * * @param flag <code>true</code> to enable tooltips, <code>false</code> to * disable tooltips. */ public void setDisplayToolTips(boolean flag) { if (flag) { ToolTipManager.sharedInstance().registerComponent(this); } else { ToolTipManager.sharedInstance().unregisterComponent(this); } } /** * Returns a string for the tooltip. * * @param e the mouse event. * * @return A tool tip or <code>null</code> if no tooltip is available. */ @Override public String getToolTipText(MouseEvent e) { String result = null; if (this.info != null) { EntityCollection entities = this.info.getEntityCollection(); if (entities != null) { Insets insets = getInsets(); ChartEntity entity = entities.getEntity( (int) ((e.getX() - insets.left) / this.scaleX), (int) ((e.getY() - insets.top) / this.scaleY)); if (entity != null) { result = entity.getToolTipText(); } } } return result; } /** * Translates a Java2D point on the chart to a screen location. * * @param java2DPoint the Java2D point. * * @return The screen location. */ public Point translateJava2DToScreen(Point2D java2DPoint) { Insets insets = getInsets(); int x = (int) (java2DPoint.getX() * this.scaleX + insets.left); int y = (int) (java2DPoint.getY() * this.scaleY + insets.top); return new Point(x, y); } /** * Translates a panel (component) location to a Java2D point. * * @param screenPoint the screen location (<code>null</code> not * permitted). * * @return The Java2D coordinates. */ public Point2D translateScreenToJava2D(Point screenPoint) { Insets insets = getInsets(); double x = (screenPoint.getX() - insets.left) / this.scaleX; double y = (screenPoint.getY() - insets.top) / this.scaleY; return new Point2D.Double(x, y); } /** * Applies any scaling that is in effect for the chart drawing to the * given rectangle. * * @param rect the rectangle (<code>null</code> not permitted). * * @return A new scaled rectangle. */ public Rectangle2D scale(Rectangle2D rect) { Insets insets = getInsets(); double x = rect.getX() * getScaleX() + insets.left; double y = rect.getY() * getScaleY() + insets.top; double w = rect.getWidth() * getScaleX(); double h = rect.getHeight() * getScaleY(); return new Rectangle2D.Double(x, y, w, h); } /** * Returns the chart entity at a given point. * <P> * This method will return null if there is (a) no entity at the given * point, or (b) no entity collection has been generated. * * @param viewX the x-coordinate. * @param viewY the y-coordinate. * * @return The chart entity (possibly <code>null</code>). */ public ChartEntity getEntityForPoint(int viewX, int viewY) { ChartEntity result = null; if (this.info != null) { Insets insets = getInsets(); double x = (viewX - insets.left) / this.scaleX; double y = (viewY - insets.top) / this.scaleY; EntityCollection entities = this.info.getEntityCollection(); result = entities != null ? entities.getEntity(x, y) : null; } return result; } /** * Returns the flag that controls whether or not the offscreen buffer * needs to be refreshed. * * @return A boolean. */ public boolean getRefreshBuffer() { return this.refreshBuffer; } /** * Sets the refresh buffer flag. This flag is used to avoid unnecessary * redrawing of the chart when the offscreen image buffer is used. * * @param flag <code>true</code> indicates that the buffer should be * refreshed. */ public void setRefreshBuffer(boolean flag) { this.refreshBuffer = flag; } /** * Paints the component by drawing the chart to fill the entire component, * but allowing for the insets (which will be non-zero if a border has been * set for this component). To increase performance (at the expense of * memory), an off-screen buffer image can be used. * * @param g the graphics device for drawing on. */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); if (this.chart == null) { return; } Graphics2D g2 = (Graphics2D) g.create(); // first determine the size of the chart rendering area... Dimension size = getSize(); Insets insets = getInsets(); Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top, size.getWidth() - insets.left - insets.right, size.getHeight() - insets.top - insets.bottom); // work out if scaling is required... boolean scale = false; double drawWidth = available.getWidth(); double drawHeight = available.getHeight(); this.scaleX = 1.0; this.scaleY = 1.0; if (drawWidth < this.minimumDrawWidth) { this.scaleX = drawWidth / this.minimumDrawWidth; drawWidth = this.minimumDrawWidth; scale = true; } else if (drawWidth > this.maximumDrawWidth) { this.scaleX = drawWidth / this.maximumDrawWidth; drawWidth = this.maximumDrawWidth; scale = true; } if (drawHeight < this.minimumDrawHeight) { this.scaleY = drawHeight / this.minimumDrawHeight; drawHeight = this.minimumDrawHeight; scale = true; } else if (drawHeight > this.maximumDrawHeight) { this.scaleY = drawHeight / this.maximumDrawHeight; drawHeight = this.maximumDrawHeight; scale = true; } Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth, drawHeight); // are we using the chart buffer? if (this.useBuffer) { // do we need to resize the buffer? if ((this.chartBuffer == null) || (this.chartBufferWidth != available.getWidth()) || (this.chartBufferHeight != available.getHeight())) { this.chartBufferWidth = (int) available.getWidth(); this.chartBufferHeight = (int) available.getHeight(); GraphicsConfiguration gc = g2.getDeviceConfiguration(); this.chartBuffer = gc.createCompatibleImage( this.chartBufferWidth, this.chartBufferHeight, Transparency.TRANSLUCENT); this.refreshBuffer = true; } // do we need to redraw the buffer? if (this.refreshBuffer) { this.refreshBuffer = false; // clear the flag Rectangle2D bufferArea = new Rectangle2D.Double( 0, 0, this.chartBufferWidth, this.chartBufferHeight); // make the background of the buffer clear and transparent Graphics2D bufferG2 = (Graphics2D) this.chartBuffer.getGraphics(); Composite savedComposite = bufferG2.getComposite(); bufferG2.setComposite(AlphaComposite.getInstance( AlphaComposite.CLEAR, 0.0f)); Rectangle r = new Rectangle(0, 0, this.chartBufferWidth, this.chartBufferHeight); bufferG2.fill(r); bufferG2.setComposite(savedComposite); if (scale) { AffineTransform saved = bufferG2.getTransform(); AffineTransform st = AffineTransform.getScaleInstance( this.scaleX, this.scaleY); bufferG2.transform(st); this.chart.draw(bufferG2, chartArea, this.anchor, this.info); bufferG2.setTransform(saved); } else { this.chart.draw(bufferG2, bufferArea, this.anchor, this.info); } } // zap the buffer onto the panel... g2.drawImage(this.chartBuffer, insets.left, insets.top, this); } else { // redrawing the chart every time... AffineTransform saved = g2.getTransform(); g2.translate(insets.left, insets.top); if (scale) { AffineTransform st = AffineTransform.getScaleInstance( this.scaleX, this.scaleY); g2.transform(st); } this.chart.draw(g2, chartArea, this.anchor, this.info); g2.setTransform(saved); } for (Overlay overlay : this.overlays) { overlay.paintOverlay(g2, this); } // redraw the zoom rectangle (if present) - if useBuffer is false, // we use XOR so we can XOR the rectangle away again without redrawing // the chart drawZoomRectangle(g2, !this.useBuffer); drawSelectionShape(g2, !this.useBuffer); g2.dispose(); this.anchor = null; this.verticalTraceLine = null; this.horizontalTraceLine = null; } /** * Receives notification of changes to the chart, and redraws the chart. * * @param event details of the chart change event. */ @Override public void chartChanged(ChartChangeEvent event) { this.refreshBuffer = true; Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { Zoomable z = (Zoomable) plot; this.orientation = z.getOrientation(); } repaint(); } /** * Receives notification of a chart progress event. * * @param event the event. */ @Override public void chartProgress(ChartProgressEvent event) { // does nothing - override if necessary } /** * Handles action events generated by the popup menu. * * @param event the event. */ @Override public void actionPerformed(ActionEvent event) { String command = event.getActionCommand(); // many of the zoom methods need a screen location - all we have is // the zoomPoint, but it might be null. Here we grab the x and y // coordinates, or use defaults... double screenX = -1.0; double screenY = -1.0; if (this.zoomPoint != null) { screenX = this.zoomPoint.getX(); screenY = this.zoomPoint.getY(); } if (command.equals(PROPERTIES_COMMAND)) { doEditChartProperties(); } else if (command.equals(COPY_COMMAND)) { doCopy(); } else if (command.equals(SAVE_AS_PNG_COMMAND)) { try { doSaveAs(); } catch (IOException e) { JOptionPane.showMessageDialog(this, "I/O error occurred.", localizationResources.getString("Save_as_PNG"), JOptionPane.WARNING_MESSAGE); } } else if (command.equals(SAVE_AS_SVG_COMMAND)) { try { saveAsSVG(null); } catch (IOException e) { JOptionPane.showMessageDialog(this, "I/O error occurred.", localizationResources.getString("Save_as_SVG"), JOptionPane.WARNING_MESSAGE); } } else if (command.equals(SAVE_AS_PDF_COMMAND)) { saveAsPDF(null); } else if (command.equals(PRINT_COMMAND)) { createChartPrintJob(); } else if (command.equals(ZOOM_IN_BOTH_COMMAND)) { zoomInBoth(screenX, screenY); } else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) { zoomInDomain(screenX, screenY); } else if (command.equals(ZOOM_IN_RANGE_COMMAND)) { zoomInRange(screenX, screenY); } else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) { zoomOutBoth(screenX, screenY); } else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) { zoomOutDomain(screenX, screenY); } else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) { zoomOutRange(screenX, screenY); } else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) { restoreAutoBounds(); } else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) { restoreAutoDomainBounds(); } else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) { restoreAutoRangeBounds(); } } /** * Handles a 'mouse entered' event. This method changes the tooltip delays * of ToolTipManager.sharedInstance() to the possibly different values set * for this chart panel. * * @param e the mouse event. */ @Override public void mouseEntered(MouseEvent e) { if (!this.toolTipSettingsActive) { ToolTipManager ttm = ToolTipManager.sharedInstance(); this.originalToolTipInitialDelay = ttm.getInitialDelay(); ttm.setInitialDelay(this.toolTipInitialDelay); this.originalToolTipDismissDelay = ttm.getDismissDelay(); ttm.setDismissDelay(this.toolTipDismissDelay); this.originalToolTipReshowDelay = ttm.getReshowDelay(); ttm.setReshowDelay(this.toolTipReshowDelay); this.toolTipSettingsActive = true; } if (this.liveMouseHandler != null) { this.liveMouseHandler.mouseEntered(e); } //handle auxiliary handlers int mods = e.getModifiers(); for (AbstractMouseHandler handler : auxiliaryMouseHandlers) { if (handler.getModifier() == 0 || (mods & handler.getModifier()) == handler.getModifier()) { handler.mouseEntered(e); } } } /** * Handles a 'mouse exited' event. This method resets the tooltip delays of * ToolTipManager.sharedInstance() to their * original values in effect before mouseEntered() * * @param e the mouse event. */ @Override public void mouseExited(MouseEvent e) { if (this.toolTipSettingsActive) { // restore original tooltip dealys ToolTipManager ttm = ToolTipManager.sharedInstance(); ttm.setInitialDelay(this.originalToolTipInitialDelay); ttm.setDismissDelay(this.originalToolTipDismissDelay); ttm.setReshowDelay(this.originalToolTipReshowDelay); this.toolTipSettingsActive = false; } if (this.liveMouseHandler != null) { this.liveMouseHandler.mouseExited(e); } //handle auxiliary handlers int mods = e.getModifiers(); for (AbstractMouseHandler handler : auxiliaryMouseHandlers) { if (handler.getModifier() == 0 || (mods & handler.getModifier()) == handler.getModifier()) { handler.mouseExited(e); } } } /** * Handles a 'mouse pressed' event. * <P> * This event is the popup trigger on Unix/Linux. For Windows, the popup * trigger is the 'mouse released' event. * * @param e The mouse event. */ @Override public void mousePressed(MouseEvent e) { int mods = e.getModifiers(); if (e.isPopupTrigger()) { if (this.popup != null) { displayPopupMenu(e.getX(), e.getY()); } return; } if (this.liveMouseHandler != null) { this.liveMouseHandler.mousePressed(e); } else { AbstractMouseHandler h = null; boolean found = false; Iterator<AbstractMouseHandler> iterator = this.availableLiveMouseHandlers.iterator(); AbstractMouseHandler nomod = null; while (iterator.hasNext() && !found) { h = (AbstractMouseHandler) iterator.next(); if (h.getModifier() == 0 && nomod == null) { nomod = h; } else { found = (mods & h.getModifier()) == h.getModifier(); } } if (!found && nomod != null) { h = nomod; found = true; } if (found) { this.liveMouseHandler = h; this.liveMouseHandler.mousePressed(e); } } // handle auxiliary handlers for (AbstractMouseHandler handler : auxiliaryMouseHandlers) { if (handler.getModifier() == 0 || (mods & handler.getModifier()) == handler.getModifier()) { handler.mousePressed(e); } } } /** * Handles a 'mouse dragged' event. * * @param e the mouse event. */ @Override public void mouseDragged(MouseEvent e) { // if the popup menu has already been triggered, then ignore dragging... if (this.popup != null && this.popup.isShowing()) { return; } if (this.liveMouseHandler != null) { this.liveMouseHandler.mouseDragged(e); } //handle auxiliary handlers int mods = e.getModifiers(); for (AbstractMouseHandler handler : auxiliaryMouseHandlers) { if (handler.getModifier() == 0 || (mods & handler.getModifier()) == handler.getModifier()) { handler.mouseDragged(e); } } } /** * Handles a 'mouse released' event. On Windows, we need to check if this * is a popup trigger, but only if we haven't already been tracking a zoom * rectangle. * * @param e information about the event. */ @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { if (this.popup != null) { displayPopupMenu(e.getX(), e.getY()); } return; } if (this.liveMouseHandler != null) { this.liveMouseHandler.mouseReleased(e); } //handle auxiliary handlers int mods = e.getModifiers(); for (AbstractMouseHandler handler : auxiliaryMouseHandlers) { if (handler.getModifier() == 0 || (mods & handler.getModifier()) == handler.getModifier()) { handler.mouseReleased(e); } } } /** * Receives notification of mouse clicks on the panel. These are * translated and passed on to any registered {@link ChartMouseListener}s. * * @param event Information about the mouse event. */ @Override public void mouseClicked(MouseEvent event) { Insets insets = getInsets(); int x = (int) ((event.getX() - insets.left) / this.scaleX); int y = (int) ((event.getY() - insets.top) / this.scaleY); this.anchor = new Point2D.Double(x, y); if (this.chart == null) { return; } this.chart.setNotify(true); // force a redraw // new entity code... Object[] listeners = this.chartMouseListeners.getListeners( ChartMouseListener.class); if (listeners.length > 0) { // handle old listeners ChartEntity entity = null; if (this.info != null) { EntityCollection entities = this.info.getEntityCollection(); if (entities != null) { entity = entities.getEntity(x, y); } } ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event, entity); for (int i = listeners.length - 1; i >= 0; i -= 1) { ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent); } } // handle new mouse handler based listeners if (this.liveMouseHandler != null) { this.liveMouseHandler.mouseClicked(event); } // handle auxiliary handlers int mods = event.getModifiers(); for (AbstractMouseHandler handler : auxiliaryMouseHandlers) { if (handler.getModifier() == 0 || (mods & handler.getModifier()) == handler.getModifier()) { handler.mouseClicked(event); } } } /** * Implementation of the MouseMotionListener's method. * * @param e the event. */ @Override public void mouseMoved(MouseEvent e) { Object[] listeners = this.chartMouseListeners.getListeners( ChartMouseListener.class); if (listeners.length > 0) { Insets insets = getInsets(); int x = (int) ((e.getX() - insets.left) / this.scaleX); int y = (int) ((e.getY() - insets.top) / this.scaleY); ChartEntity entity = null; if (this.info != null) { EntityCollection entities = this.info.getEntityCollection(); if (entities != null) { entity = entities.getEntity(x, y); } } // we can only generate events if the panel's chart is not null // (see bug report 1556951) if (this.chart != null) { ChartMouseEvent event = new ChartMouseEvent(getChart(), e, entity); for (int i = listeners.length - 1; i >= 0; i -= 1) { ((ChartMouseListener) listeners[i]).chartMouseMoved(event); } } } // handle new mouse handler based listeners if (this.chart != null) { if (this.liveMouseHandler != null) { this.liveMouseHandler.mouseMoved(e); } //handle auxiliary handlers int mods = e.getModifiers(); for (AbstractMouseHandler handler : auxiliaryMouseHandlers) { if (handler.getModifier() == 0 || (mods & handler.getModifier()) == handler.getModifier()) { handler.mouseMoved(e); } } } } /** * Zooms in on an anchor point (specified in screen coordinate space). * * @param x the x value (in screen coordinates). * @param y the y value (in screen coordinates). */ public void zoomInBoth(double x, double y) { Plot plot = this.chart.getPlot(); if (plot == null) { return; } // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); zoomInDomain(x, y); zoomInRange(x, y); plot.setNotify(savedNotify); } /** * Decreases the length of the domain axis, centered about the given * coordinate on the screen. The length of the domain axis is reduced * by the value of {@link #getZoomInFactor()}. * * @param x the x coordinate (in screen coordinates). * @param y the y-coordinate (in screen coordinates). */ public void zoomInDomain(double x, double y) { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); Zoomable z = (Zoomable) plot; z.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(), translateScreenToJava2D(new Point((int) x, (int) y)), this.zoomAroundAnchor); plot.setNotify(savedNotify); } } /** * Decreases the length of the range axis, centered about the given * coordinate on the screen. The length of the range axis is reduced by * the value of {@link #getZoomInFactor()}. * * @param x the x-coordinate (in screen coordinates). * @param y the y coordinate (in screen coordinates). */ public void zoomInRange(double x, double y) { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); Zoomable z = (Zoomable) plot; z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(), translateScreenToJava2D(new Point((int) x, (int) y)), this.zoomAroundAnchor); plot.setNotify(savedNotify); } } /** * Zooms out on an anchor point (specified in screen coordinate space). * * @param x the x value (in screen coordinates). * @param y the y value (in screen coordinates). */ public void zoomOutBoth(double x, double y) { Plot plot = this.chart.getPlot(); if (plot == null) { return; } // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); zoomOutDomain(x, y); zoomOutRange(x, y); plot.setNotify(savedNotify); } /** * Increases the length of the domain axis, centered about the given * coordinate on the screen. The length of the domain axis is increased * by the value of {@link #getZoomOutFactor()}. * * @param x the x coordinate (in screen coordinates). * @param y the y-coordinate (in screen coordinates). */ public void zoomOutDomain(double x, double y) { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); Zoomable z = (Zoomable) plot; z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(), translateScreenToJava2D(new Point((int) x, (int) y)), this.zoomAroundAnchor); plot.setNotify(savedNotify); } } /** * Increases the length the range axis, centered about the given * coordinate on the screen. The length of the range axis is increased * by the value of {@link #getZoomOutFactor()}. * * @param x the x coordinate (in screen coordinates). * @param y the y-coordinate (in screen coordinates). */ public void zoomOutRange(double x, double y) { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); Zoomable z = (Zoomable) plot; z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(), translateScreenToJava2D(new Point((int) x, (int) y)), this.zoomAroundAnchor); plot.setNotify(savedNotify); } } /** * Zooms in on a selected region. * * @param selection the selected region. */ public void zoom(Rectangle2D selection) { // get the origin of the zoom selection in the Java2D space used for // drawing the chart (that is, before any scaling to fit the panel) Point2D selectOrigin = translateScreenToJava2D(new Point( (int) Math.ceil(selection.getX()), (int) Math.ceil(selection.getY()))); PlotRenderingInfo plotInfo = this.info.getPlotInfo(); Rectangle2D scaledDataArea = getScreenDataArea( (int) selection.getCenterX(), (int) selection.getCenterY()); if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) { double hLower = (selection.getMinX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth(); double hUpper = (selection.getMaxX() - scaledDataArea.getMinX()) / scaledDataArea.getWidth(); double vLower = (scaledDataArea.getMaxY() - selection.getMaxY()) / scaledDataArea.getHeight(); double vUpper = (scaledDataArea.getMaxY() - selection.getMinY()) / scaledDataArea.getHeight(); Plot p = this.chart.getPlot(); if (p instanceof Zoomable) { // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = p.isNotify(); p.setNotify(false); Zoomable z = (Zoomable) p; if (z.getOrientation() == PlotOrientation.HORIZONTAL) { z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin); z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin); } else { z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin); z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin); } p.setNotify(savedNotify); } } } /** * Restores the auto-range calculation on both axes. */ public void restoreAutoBounds() { Plot plot = this.chart.getPlot(); if (plot == null) { return; } // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); restoreAutoDomainBounds(); restoreAutoRangeBounds(); plot.setNotify(savedNotify); } /** * Restores the auto-range calculation on the domain axis. */ public void restoreAutoDomainBounds() { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { Zoomable z = (Zoomable) plot; // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); // we need to guard against this.zoomPoint being null Point2D zp = (this.zoomPoint != null ? this.zoomPoint : new Point()); z.zoomDomainAxes(0.0, this.info.getPlotInfo(), zp); plot.setNotify(savedNotify); } } /** * Restores the auto-range calculation on the range axis. */ public void restoreAutoRangeBounds() { Plot plot = this.chart.getPlot(); if (plot instanceof Zoomable) { Zoomable z = (Zoomable) plot; // here we tweak the notify flag on the plot so that only // one notification happens even though we update multiple // axes... boolean savedNotify = plot.isNotify(); plot.setNotify(false); // we need to guard against this.zoomPoint being null Point2D zp = (this.zoomPoint != null ? this.zoomPoint : new Point()); z.zoomRangeAxes(0.0, this.info.getPlotInfo(), zp); plot.setNotify(savedNotify); } } /** * Returns the data area for the chart (the area inside the axes) with the * current scaling applied (that is, the area as it appears on screen). * * @return The scaled data area. */ public Rectangle2D getScreenDataArea() { Rectangle2D dataArea = this.info.getPlotInfo().getDataArea(); Insets insets = getInsets(); double x = dataArea.getX() * this.scaleX + insets.left; double y = dataArea.getY() * this.scaleY + insets.top; double w = dataArea.getWidth() * this.scaleX; double h = dataArea.getHeight() * this.scaleY; return new Rectangle2D.Double(x, y, w, h); } /** * Returns the data area (the area inside the axes) for the plot or subplot, * with the current scaling applied. * * @param x the x-coordinate (for subplot selection). * @param y the y-coordinate (for subplot selection). * * @return The scaled data area. */ public Rectangle2D getScreenDataArea(int x, int y) { PlotRenderingInfo plotInfo = this.info.getPlotInfo(); Rectangle2D result; if (plotInfo.getSubplotCount() == 0) { result = getScreenDataArea(); } else { // get the origin of the zoom selection in the Java2D space used for // drawing the chart (that is, before any scaling to fit the panel) Point2D selectOrigin = translateScreenToJava2D(new Point(x, y)); int subplotIndex = plotInfo.getSubplotIndex(selectOrigin); if (subplotIndex == -1) { return null; } result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea()); } return result; } /** * Returns the tool tip initial delay value used inside this chart panel. * * @return An integer representing the initial delay value, in milliseconds. * * @see #setToolTipInitialDelay(int) */ public int getToolTipInitialDelay() { return this.toolTipInitialDelay; } /** * Sets the tool tip initial delay value for this chart panel. * * @param millis the delay in milliseconds. * * @see #getToolTipInitialDelay() */ public void setToolTipInitialDelay(int millis) { this.toolTipInitialDelay = millis; } /** * Returns the tool tip dismiss delay value used inside this chart panel. * * @return An integer representing the dismissal delay value, in * milliseconds. * * @see #setToolTipDismissDelay(int) */ public int getToolTipDismissDelay() { return this.toolTipDismissDelay; } /** * Sets the tool tip dismiss delay value for this chart panel. * * @param millis the delay in milliseconds. * * @see #getToolTipDismissDelay() */ public void setToolTipDismissDelay(int millis) { this.toolTipDismissDelay = millis; } /** * Returns the tool tip reshow delay value used inside this chart panel. * * @return An integer representing the reshow delay value, in milliseconds. * * @see #setToolTipReshowDelay(int) */ public int getToolTipReshowDelay() { return this.toolTipReshowDelay; } /** * Specifies the amount of time that the user has to wait in * milliseconds before a tooltip will be reshown. * * @param millis the delay in milliseconds. * * @see #getToolTipReshowDelay() */ public void setToolTipReshowDelay(int millis) { this.toolTipReshowDelay = millis; } /** * Returns the zoom in factor. * * @return The zoom in factor. * * @see #setZoomInFactor(double) */ public double getZoomInFactor() { return this.zoomInFactor; } /** * Sets the zoom in factor. * * @param factor the factor. * * @see #getZoomInFactor() */ public void setZoomInFactor(double factor) { this.zoomInFactor = factor; } /** * Returns the zoom out factor. * * @return The zoom out factor. * * @see #setZoomOutFactor(double) */ public double getZoomOutFactor() { return this.zoomOutFactor; } /** * Sets the zoom out factor. * * @param factor the factor. * * @see #getZoomOutFactor() */ public void setZoomOutFactor(double factor) { this.zoomOutFactor = factor; } /** * Draws zoom rectangle (if present). * The drawing is performed in XOR mode, therefore * when this method is called twice in a row, * the second call will completely restore the state * of the canvas. * * @param g2 the graphics device. * @param xor use XOR for drawing? */ private void drawZoomRectangle(Graphics2D g2, boolean xor) { if (this.zoomRectangle != null) { if (xor) { // Set XOR mode to draw the zoom rectangle g2.setXORMode(Color.GRAY); } if (this.fillZoomRectangle) { g2.setPaint(this.zoomFillPaint); g2.fill(this.zoomRectangle); } else { g2.setPaint(this.zoomOutlinePaint); g2.draw(this.zoomRectangle); } if (xor) { // Reset to the default 'overwrite' mode g2.setPaintMode(); } } } /** * Draws zoom rectangle (if present). The drawing is performed in XOR mode, * therefore when this method is called twice in a row, the second call will * completely restore the state of the canvas. * * @param g2 the graphics device. * @param xor use XOR for drawing? */ private void drawSelectionShape(Graphics2D g2, boolean xor) { if (this.selectionShape != null) { if (xor) { // Set XOR mode to draw the zoom rectangle g2.setXORMode(Color.gray); } if (this.selectionFillPaint != null) { g2.setPaint(this.selectionFillPaint); g2.fill(this.selectionShape); } if (this.selectionOutlinePaint != null && this.selectionOutlineStroke != null) { g2.setPaint(this.selectionOutlinePaint); g2.setStroke(this.selectionOutlineStroke); GeneralPath pp = new GeneralPath(this.selectionShape); pp.closePath(); g2.draw(pp); } if (xor) { // Reset to the default 'overwrite' mode g2.setPaintMode(); } } } /** * Displays a dialog that allows the user to edit the properties for the * current chart. * * @since 1.0.3 */ public void doEditChartProperties() { ChartEditor editor = ChartEditorManager.getChartEditor(this.chart); int result = JOptionPane.showConfirmDialog(this, editor, localizationResources.getString("Chart_Properties"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); if (result == JOptionPane.OK_OPTION) { editor.updateChart(this.chart); } } /** * Copies the current chart to the system clipboard. * * @since 1.0.13 */ public void doCopy() { Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Insets insets = getInsets(); int w = getWidth() - insets.left - insets.right; int h = getHeight() - insets.top - insets.bottom; ChartTransferable selection = new ChartTransferable(this.chart, w, h, getMinimumDrawWidth(), getMinimumDrawHeight(), getMaximumDrawWidth(), getMaximumDrawHeight(), true); systemClipboard.setContents(selection, null); } /** * Opens a file chooser and gives the user an opportunity to save the chart * in PNG format. * * @throws IOException if there is an I/O error. */ public void doSaveAs() throws IOException { JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs); FileNameExtensionFilter filter = new FileNameExtensionFilter( localizationResources.getString("PNG_Image_Files"), "png"); fileChooser.addChoosableFileFilter(filter); fileChooser.setFileFilter(filter); int option = fileChooser.showSaveDialog(this); if (option == JFileChooser.APPROVE_OPTION) { String filename = fileChooser.getSelectedFile().getPath(); if (isEnforceFileExtensions()) { if (!filename.endsWith(".png")) { filename = filename + ".png"; } } ChartUtilities.saveChartAsPNG(new File(filename), this.chart, getWidth(), getHeight()); } } /** * Saves the chart in SVG format (a filechooser will be displayed so that * the user can specify the filename). Note that this method only works * if the JFreeSVG library is on the classpath...if this library is not * present, the method will fail. */ private void saveAsSVG(File f) throws IOException { File file = f; if (file == null) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs); FileNameExtensionFilter filter = new FileNameExtensionFilter( localizationResources.getString("SVG_Files"), "svg"); fileChooser.addChoosableFileFilter(filter); fileChooser.setFileFilter(filter); int option = fileChooser.showSaveDialog(this); if (option == JFileChooser.APPROVE_OPTION) { String filename = fileChooser.getSelectedFile().getPath(); if (isEnforceFileExtensions()) { if (!filename.endsWith(".svg")) { filename = filename + ".svg"; } } file = new File(filename); if (file.exists()) { String fileExists = localizationResources.getString( "FILE_EXISTS_CONFIRM_OVERWRITE"); int response = JOptionPane.showConfirmDialog(this, fileExists, localizationResources.getString("Save_as_SVG"), JOptionPane.OK_CANCEL_OPTION); if (response == JOptionPane.CANCEL_OPTION) { file = null; } } } } if (file != null) { // use reflection to get the SVG string String svg = generateSVG(getWidth(), getHeight()); BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter(file)); writer.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); writer.write(svg + "\n"); writer.flush(); } finally { try { if (writer != null) { writer.close(); } } catch (IOException ex) { throw new RuntimeException(ex); } } } } /** * Generates a string containing a rendering of the chart in SVG format. * This feature is only supported if the JFreeSVG library is included on * the classpath. * * @return A string containing an SVG element for the current chart, or * <code>null</code> if there is a problem with the method invocation * by reflection. */ private String generateSVG(int width, int height) { Graphics2D g2 = createSVGGraphics2D(width, height); if (g2 == null) { throw new IllegalStateException("JFreeSVG library is not present."); } // we suppress shadow generation, because SVG is a vector format and // the shadow effect is applied via bitmap effects... g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true); String svg = null; Rectangle2D drawArea = new Rectangle2D.Double(0, 0, width, height); this.chart.draw(g2, drawArea); try { Method m = g2.getClass().getMethod("getSVGElement"); svg = (String) m.invoke(g2); } catch (NoSuchMethodException e) { // null will be returned } catch (SecurityException e) { // null will be returned } catch (IllegalAccessException e) { // null will be returned } catch (IllegalArgumentException e) { // null will be returned } catch (InvocationTargetException e) { // null will be returned } return svg; } private Graphics2D createSVGGraphics2D(int w, int h) { try { Class svgGraphics2d = Class.forName("org.jfree.graphics2d.svg.SVGGraphics2D"); Constructor ctor = svgGraphics2d.getConstructor(int.class, int.class); return (Graphics2D) ctor.newInstance(w, h); } catch (ClassNotFoundException ex) { return null; } catch (NoSuchMethodException ex) { return null; } catch (SecurityException ex) { return null; } catch (InstantiationException ex) { return null; } catch (IllegalAccessException ex) { return null; } catch (IllegalArgumentException ex) { return null; } catch (InvocationTargetException ex) { return null; } } /** * Saves the chart in PDF format (a filechooser will be displayed so that * the user can specify the filename). Note that this method only works * if the OrsonPDF library is on the classpath...if this library is not * present, the method will fail. */ private void saveAsPDF(File f) { File file = f; if (file == null) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs); FileNameExtensionFilter filter = new FileNameExtensionFilter( localizationResources.getString("PDF_Files"), "pdf"); fileChooser.addChoosableFileFilter(filter); fileChooser.setFileFilter(filter); int option = fileChooser.showSaveDialog(this); if (option == JFileChooser.APPROVE_OPTION) { String filename = fileChooser.getSelectedFile().getPath(); if (isEnforceFileExtensions()) { if (!filename.endsWith(".pdf")) { filename = filename + ".pdf"; } } file = new File(filename); if (file.exists()) { String fileExists = localizationResources.getString( "FILE_EXISTS_CONFIRM_OVERWRITE"); int response = JOptionPane.showConfirmDialog(this, fileExists, localizationResources.getString("Save_as_PDF"), JOptionPane.OK_CANCEL_OPTION); if (response == JOptionPane.CANCEL_OPTION) { file = null; } } } } if (file != null) { writeAsPDF(file, getWidth(), getHeight()); } } /** * Returns <code>true</code> if OrsonPDF is on the classpath, and * <code>false</code> otherwise. The OrsonPDF library can be found at * http://www.object-refinery.com/pdf/ * * @return A boolean. */ private boolean isOrsonPDFAvailable() { Class pdfDocumentClass = null; try { pdfDocumentClass = Class.forName("com.orsonpdf.PDFDocument"); } catch (ClassNotFoundException e) { // pdfDocument class will be null so the function will return false } return (pdfDocumentClass != null); } /** * Writes the current chart to the specified file in PDF format. This * will only work when the OrsonPDF library is found on the classpath. * Reflection is used to ensure there is no compile-time dependency on * OrsonPDF (which is non-free software). * * @param file the output file (<code>null</code> not permitted). * @param w the chart width. * @param h the chart height. */ private void writeAsPDF(File file, int w, int h) { if (!isOrsonPDFAvailable()) { throw new IllegalStateException( "OrsonPDF is not present on the classpath."); } ParamChecks.nullNotPermitted(file, "file"); try { Class pdfDocClass = Class.forName("com.orsonpdf.PDFDocument"); Object pdfDoc = pdfDocClass.newInstance(); Method m = pdfDocClass.getMethod("createPage", Rectangle2D.class); Rectangle2D rect = new Rectangle(w, h); Object page = m.invoke(pdfDoc, rect); Method m2 = page.getClass().getMethod("getGraphics2D"); Graphics2D g2 = (Graphics2D) m2.invoke(page); // we suppress shadow generation, because PDF is a vector format and // the shadow effect is applied via bitmap effects... g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true); Rectangle2D drawArea = new Rectangle2D.Double(0, 0, w, h); this.chart.draw(g2, drawArea); Method m3 = pdfDocClass.getMethod("writeToFile", File.class); m3.invoke(pdfDoc, file); } catch (ClassNotFoundException ex) { throw new RuntimeException(ex); } catch (InstantiationException ex) { throw new RuntimeException(ex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (NoSuchMethodException ex) { throw new RuntimeException(ex); } catch (SecurityException ex) { throw new RuntimeException(ex); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { throw new RuntimeException(ex); } } /** * Creates a print job for the chart. */ public void createChartPrintJob() { PrinterJob job = PrinterJob.getPrinterJob(); PageFormat pf = job.defaultPage(); PageFormat pf2 = job.pageDialog(pf); if (pf2 != pf) { job.setPrintable(this, pf2); if (job.printDialog()) { try { job.print(); } catch (PrinterException e) { JOptionPane.showMessageDialog(this, e); } } } } /** * Prints the chart on a single page. * * @param g the graphics context. * @param pf the page format to use. * @param pageIndex the index of the page. If not <code>0</code>, nothing * gets print. * * @return The result of printing. */ @Override public int print(Graphics g, PageFormat pf, int pageIndex) { if (pageIndex != 0) { return NO_SUCH_PAGE; } Graphics2D g2 = (Graphics2D) g; double x = pf.getImageableX(); double y = pf.getImageableY(); double w = pf.getImageableWidth(); double h = pf.getImageableHeight(); this.chart.draw(g2, new Rectangle2D.Double(x, y, w, h), this.anchor, null); return PAGE_EXISTS; } /** * Adds a listener to the list of objects listening for chart mouse events. * * @param listener the listener (<code>null</code> not permitted). */ public void addChartMouseListener(ChartMouseListener listener) { ParamChecks.nullNotPermitted(listener, "listener"); this.chartMouseListeners.add(ChartMouseListener.class, listener); } /** * Removes a listener from the list of objects listening for chart mouse * events. * * @param listener the listener. */ public void removeChartMouseListener(ChartMouseListener listener) { this.chartMouseListeners.remove(ChartMouseListener.class, listener); } /** * Returns an array of the listeners of the given type registered with the * panel. * * @param listenerType the listener type. * * @return An array of listeners. */ @Override public <T extends EventListener> T[] getListeners(Class<T> listenerType) { if (listenerType == ChartMouseListener.class) { // fetch listeners from local storage return this.chartMouseListeners.getListeners(listenerType); } return super.getListeners(listenerType); } /** * Creates a popup menu for the panel. * * @param properties include a menu item for the chart property editor. * @param save include a menu item for saving the chart. * @param print include a menu item for printing the chart. * @param zoom include menu items for zooming. * * @return The popup menu. */ protected JPopupMenu createPopupMenu(boolean properties, boolean save, boolean print, boolean zoom) { return createPopupMenu(properties, false, save, print, zoom); } /** * Creates a popup menu for the panel. * * @param properties include a menu item for the chart property editor. * @param copy include a menu item for copying to the clipboard. * @param save include a menu item for saving the chart. * @param print include a menu item for printing the chart. * @param zoom include menu items for zooming. * * @return The popup menu. * * @since 1.0.13 */ protected JPopupMenu createPopupMenu(boolean properties, boolean copy, boolean save, boolean print, boolean zoom) { JPopupMenu result = new JPopupMenu(localizationResources.getString("Chart") + ":"); boolean separator = false; if (properties) { JMenuItem propertiesItem = new JMenuItem( localizationResources.getString("Properties...")); propertiesItem.setActionCommand(PROPERTIES_COMMAND); propertiesItem.addActionListener(this); result.add(propertiesItem); separator = true; } if (copy) { if (separator) { result.addSeparator(); } JMenuItem copyItem = new JMenuItem( localizationResources.getString("Copy")); copyItem.setActionCommand(COPY_COMMAND); copyItem.addActionListener(this); result.add(copyItem); separator = !save; } if (save) { if (separator) { result.addSeparator(); } JMenu saveSubMenu = new JMenu(localizationResources.getString( "Save_as")); JMenuItem pngItem = new JMenuItem(localizationResources.getString( "PNG...")); pngItem.setActionCommand("SAVE_AS_PNG"); pngItem.addActionListener(this); saveSubMenu.add(pngItem); if (createSVGGraphics2D(10, 10) != null) { JMenuItem svgItem = new JMenuItem(localizationResources.getString( "SVG...")); svgItem.setActionCommand("SAVE_AS_SVG"); svgItem.addActionListener(this); saveSubMenu.add(svgItem); } if (isOrsonPDFAvailable()) { JMenuItem pdfItem = new JMenuItem( localizationResources.getString("PDF...")); pdfItem.setActionCommand("SAVE_AS_PDF"); pdfItem.addActionListener(this); saveSubMenu.add(pdfItem); } result.add(saveSubMenu); separator = true; } if (print) { if (separator) { result.addSeparator(); } JMenuItem printItem = new JMenuItem( localizationResources.getString("Print...")); printItem.setActionCommand(PRINT_COMMAND); printItem.addActionListener(this); result.add(printItem); separator = true; } if (zoom) { if (separator) { result.addSeparator(); } JMenu zoomInMenu = new JMenu( localizationResources.getString("Zoom_In")); this.zoomInBothMenuItem = new JMenuItem( localizationResources.getString("All_Axes")); this.zoomInBothMenuItem.setActionCommand(ZOOM_IN_BOTH_COMMAND); this.zoomInBothMenuItem.addActionListener(this); zoomInMenu.add(this.zoomInBothMenuItem); zoomInMenu.addSeparator(); this.zoomInDomainMenuItem = new JMenuItem( localizationResources.getString("Domain_Axis")); this.zoomInDomainMenuItem.setActionCommand(ZOOM_IN_DOMAIN_COMMAND); this.zoomInDomainMenuItem.addActionListener(this); zoomInMenu.add(this.zoomInDomainMenuItem); this.zoomInRangeMenuItem = new JMenuItem( localizationResources.getString("Range_Axis")); this.zoomInRangeMenuItem.setActionCommand(ZOOM_IN_RANGE_COMMAND); this.zoomInRangeMenuItem.addActionListener(this); zoomInMenu.add(this.zoomInRangeMenuItem); result.add(zoomInMenu); JMenu zoomOutMenu = new JMenu( localizationResources.getString("Zoom_Out")); this.zoomOutBothMenuItem = new JMenuItem( localizationResources.getString("All_Axes")); this.zoomOutBothMenuItem.setActionCommand(ZOOM_OUT_BOTH_COMMAND); this.zoomOutBothMenuItem.addActionListener(this); zoomOutMenu.add(this.zoomOutBothMenuItem); zoomOutMenu.addSeparator(); this.zoomOutDomainMenuItem = new JMenuItem( localizationResources.getString("Domain_Axis")); this.zoomOutDomainMenuItem.setActionCommand( ZOOM_OUT_DOMAIN_COMMAND); this.zoomOutDomainMenuItem.addActionListener(this); zoomOutMenu.add(this.zoomOutDomainMenuItem); this.zoomOutRangeMenuItem = new JMenuItem( localizationResources.getString("Range_Axis")); this.zoomOutRangeMenuItem.setActionCommand(ZOOM_OUT_RANGE_COMMAND); this.zoomOutRangeMenuItem.addActionListener(this); zoomOutMenu.add(this.zoomOutRangeMenuItem); result.add(zoomOutMenu); JMenu autoRangeMenu = new JMenu( localizationResources.getString("Auto_Range")); this.zoomResetBothMenuItem = new JMenuItem( localizationResources.getString("All_Axes")); this.zoomResetBothMenuItem.setActionCommand( ZOOM_RESET_BOTH_COMMAND); this.zoomResetBothMenuItem.addActionListener(this); autoRangeMenu.add(this.zoomResetBothMenuItem); autoRangeMenu.addSeparator(); this.zoomResetDomainMenuItem = new JMenuItem( localizationResources.getString("Domain_Axis")); this.zoomResetDomainMenuItem.setActionCommand( ZOOM_RESET_DOMAIN_COMMAND); this.zoomResetDomainMenuItem.addActionListener(this); autoRangeMenu.add(this.zoomResetDomainMenuItem); this.zoomResetRangeMenuItem = new JMenuItem( localizationResources.getString("Range_Axis")); this.zoomResetRangeMenuItem.setActionCommand( ZOOM_RESET_RANGE_COMMAND); this.zoomResetRangeMenuItem.addActionListener(this); autoRangeMenu.add(this.zoomResetRangeMenuItem); result.addSeparator(); result.add(autoRangeMenu); } return result; } /** * The idea is to modify the zooming options depending on the type of chart * being displayed by the panel. * * @param x horizontal position of the popup. * @param y vertical position of the popup. */ protected void displayPopupMenu(int x, int y) { if (this.popup == null) { return; } // go through each zoom menu item and decide whether or not to // enable it... boolean isDomainZoomable = false; boolean isRangeZoomable = false; Plot plot = (this.chart != null ? this.chart.getPlot() : null); if (plot instanceof Zoomable) { Zoomable z = (Zoomable) plot; isDomainZoomable = z.isDomainZoomable(); isRangeZoomable = z.isRangeZoomable(); } if (this.zoomInDomainMenuItem != null) { this.zoomInDomainMenuItem.setEnabled(isDomainZoomable); } if (this.zoomOutDomainMenuItem != null) { this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable); } if (this.zoomResetDomainMenuItem != null) { this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable); } if (this.zoomInRangeMenuItem != null) { this.zoomInRangeMenuItem.setEnabled(isRangeZoomable); } if (this.zoomOutRangeMenuItem != null) { this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable); } if (this.zoomResetRangeMenuItem != null) { this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable); } if (this.zoomInBothMenuItem != null) { this.zoomInBothMenuItem.setEnabled(isDomainZoomable && isRangeZoomable); } if (this.zoomOutBothMenuItem != null) { this.zoomOutBothMenuItem.setEnabled(isDomainZoomable && isRangeZoomable); } if (this.zoomResetBothMenuItem != null) { this.zoomResetBothMenuItem.setEnabled(isDomainZoomable && isRangeZoomable); } this.popup.show(this, x, y); } /** * Updates the UI for a LookAndFeel change. */ @Override public void updateUI() { // here we need to update the UI for the popup menu, if the panel // has one... if (this.popup != null) { SwingUtilities.updateComponentTreeUI(this.popup); } super.updateUI(); } /** * Provides serialization support. * * @param stream the output stream. * * @throws IOException if there is an I/O error. */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); SerialUtils.writePaint(this.zoomFillPaint, stream); SerialUtils.writePaint(this.zoomOutlinePaint, stream); SerialUtils.writeStroke(this.selectionOutlineStroke, stream); } /** * Provides serialization support. * * @param stream the input stream. * * @throws IOException if there is an I/O error. * @throws ClassNotFoundException if there is a classpath problem. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); this.zoomFillPaint = SerialUtils.readPaint(stream); this.zoomOutlinePaint = SerialUtils.readPaint(stream); this.selectionOutlineStroke = SerialUtils.readStroke(stream); // we create a new but empty chartMouseListeners list this.chartMouseListeners = new EventListenerList(); // register as a listener with sub-components... if (this.chart != null) { this.chart.addChangeListener(this); } } /** * Returns the value of the <code>useBuffer</code> flag as set in the * constructor. * * @return A boolean. */ public boolean getUseBuffer() { return this.useBuffer; } public PlotOrientation getOrientation() { return this.orientation; } /** * Adds a mouse handler. * * @param handler the handler (<code>null</code> not permitted). * * @see #removeMouseHandler(org.jfree.chart.panel.AbstractMouseHandler) */ public void addMouseHandler(AbstractMouseHandler handler) { if (handler.isLiveHandler()) { this.availableLiveMouseHandlers.add(handler); } else { this.auxiliaryMouseHandlers.add(handler); } } /** * Removes a mouse handler. * * @param handler the handler (<code>null</code> not permitted). * * @return A boolean. * * @see #addMouseHandler(org.jfree.chart.panel.AbstractMouseHandler) */ public boolean removeMouseHandler(AbstractMouseHandler handler) { if (handler.isLiveHandler()) { return this.availableLiveMouseHandlers.remove(handler); } else { return this.auxiliaryMouseHandlers.remove(handler); } } /** * Clears the 'liveMouseHandler' field. Each handler is responsible for * calling this method when they have finished handling mouse events. */ public void clearLiveMouseHandler() { this.liveMouseHandler = null; } /** * Returns the selection shape. * * @return The selection shape (possibly <code>null</code>). * * @see #setSelectionShape(java.awt.Shape) */ public Shape getSelectionShape() { return this.selectionShape; } /** * Sets the selection shape. * * @param shape the selection shape (<code>null</code> permitted). * * @see #getSelectionShape() */ public void setSelectionShape(Shape shape) { this.selectionShape = shape; } /** * Returns the selection fill paint. * * @return The selection fill paint (possibly {@code null}). * * @see #setSelectionFillPaint(java.awt.Paint) */ public Paint getSelectionFillPaint() { return this.selectionFillPaint; } /** * Sets the selection fill paint. * * @param paint the paint (<code>null</code> permitted). * * @see #getSelectionFillPaint() */ public void setSelectionFillPaint(Paint paint) { this.selectionFillPaint = paint; } /** * Returns the outline paint used to draw the selection. * * @return The paint (possibly {@code null}). */ public Paint getSelectionOutlinePaint() { return this.selectionOutlinePaint; } /** * Sets the selection outline paint. * * @param paint the paint ({@code null} permitted). * * @see #getSelectionOutlinePaint() */ public void setSelectionOutlinePaint(Paint paint) { this.selectionOutlinePaint = paint; } /** * Returns the stroke used to draw the selection outline. * * @return The stroke (possibly {@code null}). */ public Stroke getSelectionOutlineStroke() { return this.selectionOutlineStroke; } /** * Sets the selection outline stroke * * @param stroke the paint (<code>null</code> permitted). * * @see #getSelectionOutlineStroke() */ public void setSelectionOutlineStroke(Stroke stroke) { this.selectionOutlineStroke = stroke; } /** * Returns the zoom rectangle. * * @return The zoom rectangle (possibly <code>null</code>). */ public Rectangle2D getZoomRectangle() { return this.zoomRectangle; } /** * Sets the zoom rectangle for the panel. * * @param rect the rectangle (<code>null</code> permitted). */ public void setZoomRectangle(Rectangle2D rect) { this.zoomRectangle = rect; } /** * Returns the zoom handler that is installed by default in the * constructor. You can remove this handler by calling the * removeMouseHandler() method. * * @return The zoom handler. */ public ZoomHandler getZoomHandler() { return this.zoomHandler; } /** * Returns a selection manager that can be used for point or area selection. * (e.g. * {@link org.jfree.chart.panel.selectionhandler.RegionSelectionHandler * RegionSelectionHandlers}) * * @return the selection manager that has been set via setSelectionManager * or null */ public SelectionManager getSelectionManager() { return this.selectionManager; } /** * Sets the selection manager of the ChartPanel. The manager can be * retrieved via the getSelectionManager method to be used for point or * area selection. * * (e.g. * {@link org.jfree.chart.panel.selectionhandler.RegionSelectionHandler * RegionSelectionHandlers}) * * @param manager the manager. */ public void setSelectionManager(SelectionManager manager) { this.selectionManager = manager; } }