    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ---------------
    028     * ChartPanel.java
    029     * ---------------
    030     * (C) Copyright 2000-2009, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Andrzej Porebski;
    034     *                   Soren Caspersen;
    035     *                   Jonathan Nash;
    036     *                   Hans-Jurgen Greiner;
    037     *                   Andreas Schneider;
    038     *                   Daniel van Enckevort;
    039     *                   David M O'Donnell;
    040     *                   Arnaud Lelievre;
    041     *                   Matthias Rose;
    042     *                   Onno vd Akker;
    043     *                   Sergei Ivanov;
    044     *                   Ulrich Voigt - patch 2686040;
    045     *                   Alessandro Borges - patch 1460845;
    046     *
    047     * Changes (from 28-Jun-2001)
    048     * --------------------------
    049     * 28-Jun-2001 : Integrated buffering code contributed by S???ren
    050     *               Caspersen (DG);
    051     * 18-Sep-2001 : Updated header and fixed DOS encoding problem (DG);
    052     * 22-Nov-2001 : Added scaling to improve display of charts in small sizes (DG);
    053     * 26-Nov-2001 : Added property editing, saving and printing (DG);
    054     * 11-Dec-2001 : Transferred saveChartAsPNG method to new ChartUtilities
    055     *               class (DG);
    056     * 13-Dec-2001 : Added tooltips (DG);
    057     * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
    058     *               Jonathan Nash. Renamed the tooltips class (DG);
    059     * 23-Jan-2002 : Implemented zooming based on code by Hans-Jurgen Greiner (DG);
    060     * 05-Feb-2002 : Improved tooltips setup.  Renamed method attemptSaveAs()
    061     *               --> doSaveAs() and made it public rather than private (DG);
    062     * 28-Mar-2002 : Added a new constructor (DG);
    063     * 09-Apr-2002 : Changed initialisation of tooltip generation, as suggested by
    064     *               Hans-Jurgen Greiner (DG);
    065     * 27-May-2002 : New interactive zooming methods based on code by Hans-Jurgen
    066     *               Greiner. Renamed JFreeChartPanel --> ChartPanel, moved
    067     *               constants to ChartPanelConstants interface (DG);
    068     * 31-May-2002 : Fixed a bug with interactive zooming and added a way to
    069     *               control if the zoom rectangle is filled in or drawn as an
    070     *               outline. A mouse drag gesture towards the top left now causes
    071     *               an autoRangeBoth() and is a way to undo zooms (AS);
    072     * 11-Jun-2002 : Reinstated handleClick method call in mouseClicked() to get
    073     *               crosshairs working again (DG);
    074     * 13-Jun-2002 : Added check for null popup menu in mouseDragged method (DG);
    075     * 18-Jun-2002 : Added get/set methods for minimum and maximum chart
    076     *               dimensions (DG);
    077     * 25-Jun-2002 : Removed redundant code (DG);
    078     * 27-Aug-2002 : Added get/set methods for popup menu (DG);
    079     * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    080     * 22-Oct-2002 : Added translation methods for screen <--> Java2D, contributed
    081     *               by Daniel van Enckevort (DG);
    082     * 05-Nov-2002 : Added a chart reference to the ChartMouseEvent class (DG);
    083     * 22-Nov-2002 : Added test in zoom method for inverted axes, supplied by
    084     *               David M O'Donnell (DG);
    085     * 14-Jan-2003 : Implemented ChartProgressListener interface (DG);
    086     * 14-Feb-2003 : Removed deprecated setGenerateTooltips method (DG);
    087     * 12-Mar-2003 : Added option to enforce filename extension (see bug id
    088     *               643173) (DG);
    089     * 08-Sep-2003 : Added internationalization via use of properties
    090     *               resourceBundle (RFE 690236) (AL);
    091     * 18-Sep-2003 : Added getScaleX() and getScaleY() methods (protected) as
    092     *               requested by Irv Thomae (DG);
    093     * 12-Nov-2003 : Added zooming support for the FastScatterPlot class (DG);
    094     * 24-Nov-2003 : Minor Javadoc updates (DG);
    095     * 04-Dec-2003 : Added anchor point for crosshair calculation (DG);
    096     * 17-Jan-2004 : Added new methods to set tooltip delays to be used in this
    097     *               chart panel. Refer to patch 877565 (MR);
    098     * 02-Feb-2004 : Fixed bug in zooming trigger and added zoomTriggerDistance
    099     *               attribute (DG);
    100     * 08-Apr-2004 : Changed getScaleX() and getScaleY() from protected to
    101     *               public (DG);
    102     * 15-Apr-2004 : Added zoomOutFactor and zoomInFactor (DG);
    103     * 21-Apr-2004 : Fixed zooming bug in mouseReleased() method (DG);
    104     * 13-Jul-2004 : Added check for null chart (DG);
    105     * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
    106     * 11-Nov-2004 : Moved constants back in from ChartPanelConstants (DG);
    107     * 12-Nov-2004 : Modified zooming mechanism to support zooming within
    108     *               subplots (DG);
    109     * 26-Jan-2005 : Fixed mouse zooming for horizontal category plots (DG);
    110     * 11-Apr-2005 : Added getFillZoomRectangle() method, renamed
    111     *               setHorizontalZoom() --> setDomainZoomable(),
    112     *               setVerticalZoom() --> setRangeZoomable(), added
    113     *               isDomainZoomable() and isRangeZoomable(), added
    114     *               getHorizontalAxisTrace() and getVerticalAxisTrace(),
    115     *               renamed autoRangeBoth() --> restoreAutoBounds(),
    116     *               autoRangeHorizontal() --> restoreAutoDomainBounds(),
    117     *               autoRangeVertical() --> restoreAutoRangeBounds() (DG);
    118     * 12-Apr-2005 : Removed working areas, added getAnchorPoint() method,
    119     *               added protected accessors for tracelines (DG);
    120     * 18-Apr-2005 : Made constants final (DG);
    121     * 26-Apr-2005 : Removed LOGGER (DG);
    122     * 01-Jun-2005 : Fixed zooming for combined plots - see bug report
    123     *               1212039, fix thanks to Onno vd Akker (DG);
    124     * 25-Nov-2005 : Reworked event listener mechanism (DG);
    125     * ------------- JFREECHART 1.0.x ---------------------------------------------
    126     * 01-Aug-2006 : Fixed minor bug in restoreAutoRangeBounds() (DG);
    127     * 04-Sep-2006 : Renamed attemptEditChartProperties() -->
    128     *               doEditChartProperties() and made public (DG);
    129     * 13-Sep-2006 : Don't generate ChartMouseEvents if the panel's chart is null
    130     *               (fixes bug 1556951) (DG);
    131     * 05-Mar-2007 : Applied patch 1672561 by Sergei Ivanov, to fix zoom rectangle
    132     *               drawing for dynamic charts (DG);
    133     * 17-Apr-2007 : Fix NullPointerExceptions in zooming for combined plots (DG);
    134     * 24-May-2007 : When the look-and-feel changes, update the popup menu if there
    135     *               is one (DG);
    136     * 06-Jun-2007 : Fixed coordinates for drawing buffer image (DG);
    137     * 24-Sep-2007 : Added zoomAroundAnchor flag, and handle clearing of chart
    138     *               buffer (DG);
    139     * 25-Oct-2007 : Added default directory attribute (DG);
    140     * 07-Nov-2007 : Fixed (rare) bug in refreshing off-screen image (DG);
    141     * 07-May-2008 : Fixed bug in zooming that triggered zoom for a rectangle
    142     *               outside of the data area (DG);
    143     * 08-May-2008 : Fixed serialization bug (DG);
    144     * 15-Aug-2008 : Increased default maxDrawWidth/Height (DG);
    145     * 18-Sep-2008 : Modified creation of chart buffer (DG);
    146     * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
    147     *               Jess Thrysoee (DG);
    148     * 13-Jan-2009 : Fixed zooming methods to trigger only one plot
    149     *               change event (DG);
    150     * 16-Jan-2009 : Use XOR for zoom rectangle only if useBuffer is false (DG);
    151     * 18-Mar-2009 : Added mouse wheel support (DG);
    152     * 19-Mar-2009 : Added panning on mouse drag support - based on Ulrich
    153     *               Voigt's patch 2686040 (DG);
    154     * 26-Mar-2009 : Changed fillZoomRectangle default to true, and only change
    155     *               cursor for CTRL-mouse-click if panning is enabled (DG);
    156     * 01-Apr-2009 : Fixed panning, and added different mouse event mask for
    157     *               MacOSX (DG);
    158     * 08-Apr-2009 : Added copy to clipboard support, based on patch 1460845
    159     *               by Alessandro Borges (DG);
    160     * 09-Apr-2009 : Added overlay support (DG);
    161     * 10-Apr-2009 : Set chartBuffer background to match ChartPanel (DG);
    162     *
    163     */
    164
    165    package org.jfree.chart;
    166
    167    import java.awt.AWTEvent;
    168    import java.awt.Color;
    169    import java.awt.Cursor;
    170    import java.awt.Dimension;
    171    import java.awt.Graphics;
    172    import java.awt.Graphics2D;
    173    import java.awt.GraphicsConfiguration;
    174    import java.awt.Image;
    175    import java.awt.Insets;
    176    import java.awt.Paint;
    177    import java.awt.Point;
    178    import java.awt.Rectangle;
    179    import java.awt.Toolkit;
    180    import java.awt.Transparency;
    181    import java.awt.datatransfer.Clipboard;
    182    import java.awt.event.ActionEvent;
    183    import java.awt.event.ActionListener;
    184    import java.awt.event.InputEvent;
    185    import java.awt.event.MouseEvent;
    186    import java.awt.event.MouseListener;
    187    import java.awt.event.MouseMotionListener;
    188    import java.awt.geom.AffineTransform;
    189    import java.awt.geom.Line2D;
    190    import java.awt.geom.Point2D;
    191    import java.awt.geom.Rectangle2D;
    192    import java.awt.print.PageFormat;
    193    import java.awt.print.Printable;
    194    import java.awt.print.PrinterException;
    195    import java.awt.print.PrinterJob;
    196    import java.io.File;
    197    import java.io.IOException;
    198    import java.io.ObjectInputStream;
    199    import java.io.ObjectOutputStream;
    200    import java.io.Serializable;
    201    import java.lang.reflect.Constructor;
    202    import java.lang.reflect.InvocationTargetException;
    203    import java.lang.reflect.Method;
    204    import java.util.EventListener;
    205    import java.util.Iterator;
    206    import java.util.List;
    207    import java.util.ResourceBundle;
    208
    209    import javax.swing.JFileChooser;
    210    import javax.swing.JMenu;
    211    import javax.swing.JMenuItem;
    212    import javax.swing.JOptionPane;
    213    import javax.swing.JPanel;
    214    import javax.swing.JPopupMenu;
    215    import javax.swing.SwingUtilities;
    216    import javax.swing.ToolTipManager;
    217    import javax.swing.event.EventListenerList;
    218
    219    import org.jfree.chart.editor.ChartEditor;
    220    import org.jfree.chart.editor.ChartEditorManager;
    221    import org.jfree.chart.entity.ChartEntity;
    222    import org.jfree.chart.entity.EntityCollection;
    223    import org.jfree.chart.event.ChartChangeEvent;
    224    import org.jfree.chart.event.ChartChangeListener;
    225    import org.jfree.chart.event.ChartProgressEvent;
    226    import org.jfree.chart.event.ChartProgressListener;
    227    import org.jfree.chart.panel.Overlay;
    228    import org.jfree.chart.event.OverlayChangeEvent;
    229    import org.jfree.chart.event.OverlayChangeListener;
    230    import org.jfree.chart.plot.Pannable;
    231    import org.jfree.chart.plot.Plot;
    232    import org.jfree.chart.plot.PlotOrientation;
    233    import org.jfree.chart.plot.PlotRenderingInfo;
    234    import org.jfree.chart.plot.Zoomable;
    235    import org.jfree.chart.util.ResourceBundleWrapper;
    236    import org.jfree.io.SerialUtilities;
    237    import org.jfree.ui.ExtensionFileFilter;
    238
    239    /**
    240     * A Swing GUI component for displaying a {@link JFreeChart} object.
    241     * <P>
    242     * The panel registers with the chart to receive notification of changes to any
    243     * component of the chart.  The chart is redrawn automatically whenever this
    244     * notification is received.
    245     */
    246    public class ChartPanel extends JPanel implements ChartChangeListener,
    247            ChartProgressListener, ActionListener, MouseListener,
    248            MouseMotionListener, OverlayChangeListener, Printable, Serializable {
    249
    250        /** For serialization. */
    251        private static final long serialVersionUID = 6046366297214274674L;
    252
    253        /**
    254         * Default setting for buffer usage.  The default has been changed to
    255         * <code>true</code> from version 1.0.13 onwards, because of a severe
    256         * performance problem with drawing the zoom rectangle using XOR (which
    257         * now happens only when the buffer is NOT used).
    258         */
    259        public static final boolean DEFAULT_BUFFER_USED = true;
    260
    261        /** The default panel width. */
    262        public static final int DEFAULT_WIDTH = 680;
    263
    264        /** The default panel height. */
    265        public static final int DEFAULT_HEIGHT = 420;
    266
    267        /** The default limit below which chart scaling kicks in. */
    268        public static final int DEFAULT_MINIMUM_DRAW_WIDTH = 300;
    269
    270        /** The default limit below which chart scaling kicks in. */
    271        public static final int DEFAULT_MINIMUM_DRAW_HEIGHT = 200;
    272
    273        /** The default limit above which chart scaling kicks in. */
    274        public static final int DEFAULT_MAXIMUM_DRAW_WIDTH = 1024;
    275
    276        /** The default limit above which chart scaling kicks in. */
    277        public static final int DEFAULT_MAXIMUM_DRAW_HEIGHT = 768;
    278
    279        /** The minimum size required to perform a zoom on a rectangle */
    280        public static final int DEFAULT_ZOOM_TRIGGER_DISTANCE = 10;
    281
    282        /** Properties action command. */
    283        public static final String PROPERTIES_COMMAND = "PROPERTIES";
    284
    285        /**
    286         * Copy action command.
    287         *
    288         * @since 1.0.13
    289         */
    290        public static final String COPY_COMMAND = "COPY";
    291
    292        /** Save action command. */
    293        public static final String SAVE_COMMAND = "SAVE";
    294
    295        /** Print action command. */
    296        public static final String PRINT_COMMAND = "PRINT";
    297
    298        /** Zoom in (both axes) action command. */
    299        public static final String ZOOM_IN_BOTH_COMMAND = "ZOOM_IN_BOTH";
    300
    301        /** Zoom in (domain axis only) action command. */
    302        public static final String ZOOM_IN_DOMAIN_COMMAND = "ZOOM_IN_DOMAIN";
    303
    304        /** Zoom in (range axis only) action command. */
    305        public static final String ZOOM_IN_RANGE_COMMAND = "ZOOM_IN_RANGE";
    306
    307        /** Zoom out (both axes) action command. */
    308        public static final String ZOOM_OUT_BOTH_COMMAND = "ZOOM_OUT_BOTH";
    309
    310        /** Zoom out (domain axis only) action command. */
    311        public static final String ZOOM_OUT_DOMAIN_COMMAND = "ZOOM_DOMAIN_BOTH";
    312
    313        /** Zoom out (range axis only) action command. */
    314        public static final String ZOOM_OUT_RANGE_COMMAND = "ZOOM_RANGE_BOTH";
    315
    316        /** Zoom reset (both axes) action command. */
    317        public static final String ZOOM_RESET_BOTH_COMMAND = "ZOOM_RESET_BOTH";
    318
    319        /** Zoom reset (domain axis only) action command. */
    320        public static final String ZOOM_RESET_DOMAIN_COMMAND = "ZOOM_RESET_DOMAIN";
    321
    322        /** Zoom reset (range axis only) action command. */
    323        public static final String ZOOM_RESET_RANGE_COMMAND = "ZOOM_RESET_RANGE";
    324
    325        /** The chart that is displayed in the panel. */
    326        private JFreeChart chart;
    327
    328        /** Storage for registered (chart) mouse listeners. */
    329        private transient EventListenerList chartMouseListeners;
    330
    331        /** A flag that controls whether or not the off-screen buffer is used. */
    332        private boolean useBuffer;
    333
    334        /** A flag that indicates that the buffer should be refreshed. */
    335        private boolean refreshBuffer;
    336
    337        /** A buffer for the rendered chart. */
    338        private transient Image chartBuffer;
    339
    340        /** The height of the chart buffer. */
    341        private int chartBufferHeight;
    342
    343        /** The width of the chart buffer. */
    344        private int chartBufferWidth;
    345
    346        /**
    347         * The minimum width for drawing a chart (uses scaling for smaller widths).
    348         */
    349        private int minimumDrawWidth;
    350
    351        /**
    352         * The minimum height for drawing a chart (uses scaling for smaller
    353         * heights).
    354         */
    355        private int minimumDrawHeight;
    356
    357        /**
    358         * The maximum width for drawing a chart (uses scaling for bigger
    359         * widths).
    360         */
    361        private int maximumDrawWidth;
    362
    363        /**
    364         * The maximum height for drawing a chart (uses scaling for bigger
    365         * heights).
    366         */
    367        private int maximumDrawHeight;
    368
    369        /** The popup menu for the frame. */
    370        private JPopupMenu popup;
    371
    372        /** The drawing info collected the last time the chart was drawn. */
    373        private ChartRenderingInfo info;
    374
    375        /** The chart anchor point. */
    376        private Point2D anchor;
    377
    378        /** The scale factor used to draw the chart. */
    379        private double scaleX;
    380
    381        /** The scale factor used to draw the chart. */
    382        private double scaleY;
    383
    384        /** The plot orientation. */
    385        private PlotOrientation orientation = PlotOrientation.VERTICAL;
    386
    387        /** A flag that controls whether or not domain zooming is enabled. */
    388        private boolean domainZoomable = false;
    389
    390        /** A flag that controls whether or not range zooming is enabled. */
    391        private boolean rangeZoomable = false;
    392
    393        /**
    394         * The zoom rectangle starting point (selected by the user with a mouse
    395         * click).  This is a point on the screen, not the chart (which may have
    396         * been scaled up or down to fit the panel).
    397         */
    398        private Point2D zoomPoint = null;
    399
    400        /** The zoom rectangle (selected by the user with the mouse). */
    401        private transient Rectangle2D zoomRectangle = null;
    402
    403        /** Controls if the zoom rectangle is drawn as an outline or filled. */
    404        private boolean fillZoomRectangle = true;
    405
    406        /** The minimum distance required to drag the mouse to trigger a zoom. */
    407        private int zoomTriggerDistance;
    408
    409        /** A flag that controls whether or not horizontal tracing is enabled. */
    410        private boolean horizontalAxisTrace = false;
    411
    412        /** A flag that controls whether or not vertical tracing is enabled. */
    413        private boolean verticalAxisTrace = false;
    414
    415        /** A vertical trace line. */
    416        private transient Line2D verticalTraceLine;
    417
    418        /** A horizontal trace line. */
    419        private transient Line2D horizontalTraceLine;
    420
    421        /** Menu item for zooming in on a chart (both axes). */
    422        private JMenuItem zoomInBothMenuItem;
    423
    424        /** Menu item for zooming in on a chart (domain axis). */
    425        private JMenuItem zoomInDomainMenuItem;
    426
    427        /** Menu item for zooming in on a chart (range axis). */
    428        private JMenuItem zoomInRangeMenuItem;
    429
    430        /** Menu item for zooming out on a chart. */
    431        private JMenuItem zoomOutBothMenuItem;
    432
    433        /** Menu item for zooming out on a chart (domain axis). */
    434        private JMenuItem zoomOutDomainMenuItem;
    435
    436        /** Menu item for zooming out on a chart (range axis). */
    437        private JMenuItem zoomOutRangeMenuItem;
    438
    439        /** Menu item for resetting the zoom (both axes). */
    440        private JMenuItem zoomResetBothMenuItem;
    441
    442        /** Menu item for resetting the zoom (domain axis only). */
    443        private JMenuItem zoomResetDomainMenuItem;
    444
    445        /** Menu item for resetting the zoom (range axis only). */
    446        private JMenuItem zoomResetRangeMenuItem;
    447
    448        /**
    449         * The default directory for saving charts to file.
    450         *
    451         * @since 1.0.7
    452         */
    453        private File defaultDirectoryForSaveAs;
    454
    455        /** A flag that controls whether or not file extensions are enforced. */
    456        private boolean enforceFileExtensions;
    457
    458        /** A flag that indicates if original tooltip delays are changed. */
    459        private boolean ownToolTipDelaysActive;
    460
    461        /** Original initial tooltip delay of ToolTipManager.sharedInstance(). */
    462        private int originalToolTipInitialDelay;
    463
    464        /** Original reshow tooltip delay of ToolTipManager.sharedInstance(). */
    465        private int originalToolTipReshowDelay;
    466
    467        /** Original dismiss tooltip delay of ToolTipManager.sharedInstance(). */
    468        private int originalToolTipDismissDelay;
    469
    470        /** Own initial tooltip delay to be used in this chart panel. */
    471        private int ownToolTipInitialDelay;
    472
    473        /** Own reshow tooltip delay to be used in this chart panel. */
    474        private int ownToolTipReshowDelay;
    475
    476        /** Own dismiss tooltip delay to be used in this chart panel. */
    477        private int ownToolTipDismissDelay;
    478
    479        /** The factor used to zoom in on an axis range. */
    480        private double zoomInFactor = 0.5;
    481
    482        /** The factor used to zoom out on an axis range. */
    483        private double zoomOutFactor = 2.0;
    484
    485        /**
    486         * A flag that controls whether zoom operations are centred on the
    487         * current anchor point, or the centre point of the relevant axis.
    488         *
    489         * @since 1.0.7
    490         */
    491        private boolean zoomAroundAnchor;
    492
    493        /**
    494         * The paint used to draw the zoom rectangle outline.
    495         *
    496         * @since 1.0.13
    497         */
    498        private transient Paint zoomOutlinePaint;
    499
    500        /**
    501         * The zoom fill paint (should use transparency).
    502         *
    503         * @since 1.0.13
    504         */
    505        private transient Paint zoomFillPaint;
    506
    507        /** The resourceBundle for the localization. */
    508        protected static ResourceBundle localizationResources
    509                = ResourceBundleWrapper.getBundle(
    510                        "org.jfree.chart.LocalizationBundle");
    511
    512        /**
    513         * Temporary storage for the width and height of the chart
    514         * drawing area during panning.
    515         */
    516        private double panW, panH;
    517
    518        /** The last mouse position during panning. */
    519        private Point panLast;
    520
    521        /**
    522         * The mask for mouse events to trigger panning.
    523         *
    524         * @since 1.0.13
    525         */
    526        private int panMask = InputEvent.CTRL_MASK;
    527
    528        /**
    529         * A list of overlays for the panel.
    530         *
    531         * @since 1.0.13
    532         */
    533        private List overlays;
    534
    535        /**
    536         * Constructs a panel that displays the specified chart.
    537         *
    538         * @param chart  the chart.
    539         */
    540        public ChartPanel(JFreeChart chart) {
    541
    542            this(
    543                chart,
    544                DEFAULT_WIDTH,
    545                DEFAULT_HEIGHT,
    546                DEFAULT_MINIMUM_DRAW_WIDTH,
    547                DEFAULT_MINIMUM_DRAW_HEIGHT,
    548                DEFAULT_MAXIMUM_DRAW_WIDTH,
    549                DEFAULT_MAXIMUM_DRAW_HEIGHT,
    550                DEFAULT_BUFFER_USED,
    551                true,  // properties
    552                true,  // save
    553                true,  // print
    554                true,  // zoom
    555                true   // tooltips
    556            );
    557
    558        }
    559
    560        /**
    561         * Constructs a panel containing a chart.  The <code>useBuffer</code> flag
    562         * controls whether or not an offscreen <code>BufferedImage</code> is
    563         * maintained for the chart.  If the buffer is used, more memory is
    564         * consumed, but panel repaints will be a lot quicker in cases where the
    565         * chart itself hasn't changed (for example, when another frame is moved
    566         * to reveal the panel).  WARNING: If you set the <code>useBuffer</code>
    567         * flag to false, note that the mouse zooming rectangle will (in that case)
    568         * be drawn using XOR, and there is a SEVERE performance problem with that
    569         * on JRE6 on Windows.
    570         *
    571         * @param chart  the chart.
    572         * @param useBuffer  a flag controlling whether or not an off-screen buffer
    573         *                   is used (read the warning above before setting this
    574         *                   to <code>false</code>).
    575         */
    576        public ChartPanel(JFreeChart chart, boolean useBuffer) {
    577
    578            this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH,
    579                    DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH,
    580                    DEFAULT_MAXIMUM_DRAW_HEIGHT, useBuffer,
    581                    true,  // properties
    582                    true,  // save
    583                    true,  // print
    584                    true,  // zoom
    585                    true   // tooltips
    586                    );
    587
    588        }
    589
    590        /**
    591         * Constructs a JFreeChart panel.
    592         *
    593         * @param chart  the chart.
    594         * @param properties  a flag indicating whether or not the chart property
    595         *                    editor should be available via the popup menu.
    596         * @param save  a flag indicating whether or not save options should be
    597         *              available via the popup menu.
    598         * @param print  a flag indicating whether or not the print option
    599         *               should be available via the popup menu.
    600         * @param zoom  a flag indicating whether or not zoom options should
    601         *              be added to the popup menu.
    602         * @param tooltips  a flag indicating whether or not tooltips should be
    603         *                  enabled for the chart.
    604         */
    605        public ChartPanel(JFreeChart chart,
    606                          boolean properties,
    607                          boolean save,
    608                          boolean print,
    609                          boolean zoom,
    610                          boolean tooltips) {
    611
    612            this(chart,
    613                 DEFAULT_WIDTH,
    614                 DEFAULT_HEIGHT,
    615                 DEFAULT_MINIMUM_DRAW_WIDTH,
    616                 DEFAULT_MINIMUM_DRAW_HEIGHT,
    617                 DEFAULT_MAXIMUM_DRAW_WIDTH,
    618                 DEFAULT_MAXIMUM_DRAW_HEIGHT,
    619                 DEFAULT_BUFFER_USED,
    620                 properties,
    621                 save,
    622                 print,
    623                 zoom,
    624                 tooltips
    625                 );
    626
    627        }
    628
    629        /**
    630         * Constructs a JFreeChart panel.
    631         *
    632         * @param chart  the chart.
    633         * @param width  the preferred width of the panel.
    634         * @param height  the preferred height of the panel.
    635         * @param minimumDrawWidth  the minimum drawing width.
    636         * @param minimumDrawHeight  the minimum drawing height.
    637         * @param maximumDrawWidth  the maximum drawing width.
    638         * @param maximumDrawHeight  the maximum drawing height.
    639         * @param useBuffer  a flag that indicates whether to use the off-screen
    640         *                   buffer to improve performance (at the expense of
    641         *                   memory).
    642         * @param properties  a flag indicating whether or not the chart property
    643         *                    editor should be available via the popup menu.
    644         * @param save  a flag indicating whether or not save options should be
    645         *              available via the popup menu.
    646         * @param print  a flag indicating whether or not the print option
    647         *               should be available via the popup menu.
    648         * @param zoom  a flag indicating whether or not zoom options should be
    649         *              added to the popup menu.
    650         * @param tooltips  a flag indicating whether or not tooltips should be
    651         *                  enabled for the chart.
    652         */
    653        public ChartPanel(JFreeChart chart, int width, int height,
    654                int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth,
    655                int maximumDrawHeight, boolean useBuffer, boolean properties,
    656                boolean save, boolean print, boolean zoom, boolean tooltips) {
    657
    658            this(chart, width, height, minimumDrawWidth, minimumDrawHeight,
    659                    maximumDrawWidth, maximumDrawHeight, useBuffer, properties,
    660                    true, save, print, zoom, tooltips);
    661        }
    662
    663        /**
    664         * Constructs a JFreeChart panel.
    665         *
    666         * @param chart  the chart.
    667         * @param width  the preferred width of the panel.
    668         * @param height  the preferred height of the panel.
    669         * @param minimumDrawWidth  the minimum drawing width.
    670         * @param minimumDrawHeight  the minimum drawing height.
    671         * @param maximumDrawWidth  the maximum drawing width.
    672         * @param maximumDrawHeight  the maximum drawing height.
    673         * @param useBuffer  a flag that indicates whether to use the off-screen
    674         *                   buffer to improve performance (at the expense of
    675         *                   memory).
    676         * @param properties  a flag indicating whether or not the chart property
    677         *                    editor should be available via the popup menu.
    678         * @param copy  a flag indicating whether or not a copy option should be
    679         *              available via the popup menu.
    680         * @param save  a flag indicating whether or not save options should be
    681         *              available via the popup menu.
    682         * @param print  a flag indicating whether or not the print option
    683         *               should be available via the popup menu.
    684         * @param zoom  a flag indicating whether or not zoom options should be
    685         *              added to the popup menu.
    686         * @param tooltips  a flag indicating whether or not tooltips should be
    687         *                  enabled for the chart.
    688         *
    689         * @since 1.0.13
    690         */
    691        public ChartPanel(JFreeChart chart, int width, int height,
    692               int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth,
    693               int maximumDrawHeight, boolean useBuffer, boolean properties,
    694               boolean copy, boolean save, boolean print, boolean zoom,
    695               boolean tooltips) {
    696
    697            setChart(chart);
    698            this.chartMouseListeners = new EventListenerList();
    699            this.info = new ChartRenderingInfo();
    700            setPreferredSize(new Dimension(width, height));
    701            this.useBuffer = useBuffer;
    702            this.refreshBuffer = false;
    703            this.minimumDrawWidth = minimumDrawWidth;
    704            this.minimumDrawHeight = minimumDrawHeight;
    705            this.maximumDrawWidth = maximumDrawWidth;
    706            this.maximumDrawHeight = maximumDrawHeight;
    707            this.zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE;
    708
    709            // set up popup menu...
    710            this.popup = null;
    711            if (properties || copy || save || print || zoom) {
    712                this.popup = createPopupMenu(properties, copy, save, print, zoom);
    713            }
    714
    715            enableEvents(AWTEvent.MOUSE_EVENT_MASK);
    716            enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
    717            setDisplayToolTips(tooltips);
    718            addMouseListener(this);
    719            addMouseMotionListener(this);
    720
    721            this.defaultDirectoryForSaveAs = null;
    722            this.enforceFileExtensions = true;
    723
    724            // initialize ChartPanel-specific tool tip delays with
    725            // values the from ToolTipManager.sharedInstance()
    726            ToolTipManager ttm = ToolTipManager.sharedInstance();
    727            this.ownToolTipInitialDelay = ttm.getInitialDelay();
    728            this.ownToolTipDismissDelay = ttm.getDismissDelay();
    729            this.ownToolTipReshowDelay = ttm.getReshowDelay();
    730
    731            this.zoomAroundAnchor = false;
    732            this.zoomOutlinePaint = Color.blue;
    733            this.zoomFillPaint = new Color(0, 0, 255, 63);
    734
    735            this.panMask = InputEvent.CTRL_MASK;
    736            // for MacOSX we can't use the CTRL key for mouse drags, see:
    737            // http://developer.apple.com/qa/qa2004/qa1362.html
    738            String osName = System.getProperty("os.name").toLowerCase();
    739            if (osName.startsWith("mac os x")) {
    740                this.panMask = InputEvent.ALT_MASK;
    741            }
    742
    743            this.overlays = new java.util.ArrayList();
    744        }
    745
    746        /**
    747         * Returns the chart contained in the panel.
    748         *
    749         * @return The chart (possibly <code>null</code>).
    750         */
    751        public JFreeChart getChart() {
    752            return this.chart;
    753        }
    754
    755        /**
    756         * Sets the chart that is displayed in the panel.
    757         *
    758         * @param chart  the chart (<code>null</code> permitted).
    759         */
    760        public void setChart(JFreeChart chart) {
    761
    762            // stop listening for changes to the existing chart
    763            if (this.chart != null) {
    764                this.chart.removeChangeListener(this);
    765                this.chart.removeProgressListener(this);
    766            }
    767
    768            // add the new chart
    769            this.chart = chart;
    770            if (chart != null) {
    771                this.chart.addChangeListener(this);
    772                this.chart.addProgressListener(this);
    773                Plot plot = chart.getPlot();
    774                this.domainZoomable = false;
    775                this.rangeZoomable = false;
    776                if (plot instanceof Zoomable) {
    777                    Zoomable z = (Zoomable) plot;
    778                    this.domainZoomable = z.isDomainZoomable();
    779                    this.rangeZoomable = z.isRangeZoomable();
    780                    this.orientation = z.getOrientation();
    781                }
    782            }
    783            else {
    784                this.domainZoomable = false;
    785                this.rangeZoomable = false;
    786            }
    787            if (this.useBuffer) {
    788                this.refreshBuffer = true;
    789            }
    790            repaint();
    791
    792        }
    793
    794        /**
    795         * Returns the minimum drawing width for charts.
    796         * <P>
    797         * If the width available on the panel is less than this, then the chart is
    798         * drawn at the minimum width then scaled down to fit.
    799         *
    800         * @return The minimum drawing width.
    801         */
    802        public int getMinimumDrawWidth() {
    803            return this.minimumDrawWidth;
    804        }
    805
    806        /**
    807         * Sets the minimum drawing width for the chart on this panel.
    808         * <P>
    809         * At the time the chart is drawn on the panel, if the available width is
    810         * less than this amount, the chart will be drawn using the minimum width
    811         * then scaled down to fit the available space.
    812         *
    813         * @param width  The width.
    814         */
    815        public void setMinimumDrawWidth(int width) {
    816            this.minimumDrawWidth = width;
    817        }
    818
    819        /**
    820         * Returns the maximum drawing width for charts.
    821         * <P>
    822         * If the width available on the panel is greater than this, then the chart
    823         * is drawn at the maximum width then scaled up to fit.
    824         *
    825         * @return The maximum drawing width.
    826         */
    827        public int getMaximumDrawWidth() {
    828            return this.maximumDrawWidth;
    829        }
    830
    831        /**
    832         * Sets the maximum drawing width for the chart on this panel.
    833         * <P>
    834         * At the time the chart is drawn on the panel, if the available width is
    835         * greater than this amount, the chart will be drawn using the maximum
    836         * width then scaled up to fit the available space.
    837         *
    838         * @param width  The width.
    839         */
    840        public void setMaximumDrawWidth(int width) {
    841            this.maximumDrawWidth = width;
    842        }
    843
    844        /**
    845         * Returns the minimum drawing height for charts.
    846         * <P>
    847         * If the height available on the panel is less than this, then the chart
    848         * is drawn at the minimum height then scaled down to fit.
    849         *
    850         * @return The minimum drawing height.
    851         */
    852        public int getMinimumDrawHeight() {
    853            return this.minimumDrawHeight;
    854        }
    855
    856        /**
    857         * Sets the minimum drawing height for the chart on this panel.
    858         * <P>
    859         * At the time the chart is drawn on the panel, if the available height is
    860         * less than this amount, the chart will be drawn using the minimum height
    861         * then scaled down to fit the available space.
    862         *
    863         * @param height  The height.
    864         */
    865        public void setMinimumDrawHeight(int height) {
    866            this.minimumDrawHeight = height;
    867        }
    868
    869        /**
    870         * Returns the maximum drawing height for charts.
    871         * <P>
    872         * If the height available on the panel is greater than this, then the
    873         * chart is drawn at the maximum height then scaled up to fit.
    874         *
    875         * @return The maximum drawing height.
    876         */
    877        public int getMaximumDrawHeight() {
    878            return this.maximumDrawHeight;
    879        }
    880
    881        /**
    882         * Sets the maximum drawing height for the chart on this panel.
    883         * <P>
    884         * At the time the chart is drawn on the panel, if the available height is
    885         * greater than this amount, the chart will be drawn using the maximum
    886         * height then scaled up to fit the available space.
    887         *
    888         * @param height  The height.
    889         */
    890        public void setMaximumDrawHeight(int height) {
    891            this.maximumDrawHeight = height;
    892        }
    893
    894        /**
    895         * Returns the X scale factor for the chart.  This will be 1.0 if no
    896         * scaling has been used.
    897         *
    898         * @return The scale factor.
    899         */
    900        public double getScaleX() {
    901            return this.scaleX;
    902        }
    903
    904        /**
    905         * Returns the Y scale factory for the chart.  This will be 1.0 if no
    906         * scaling has been used.
    907         *
    908         * @return The scale factor.
    909         */
    910        public double getScaleY() {
    911            return this.scaleY;
    912        }
    913
    914        /**
    915         * Returns the anchor point.
    916         *
    917         * @return The anchor point (possibly <code>null</code>).
    918         */
    919        public Point2D getAnchor() {
    920            return this.anchor;
    921        }
    922
    923        /**
    924         * Sets the anchor point.  This method is provided for the use of
    925         * subclasses, not end users.
    926         *
    927         * @param anchor  the anchor point (<code>null</code> permitted).
    928         */
    929        protected void setAnchor(Point2D anchor) {
    930            this.anchor = anchor;
    931        }
    932
    933        /**
    934         * Returns the popup menu.
    935         *
    936         * @return The popup menu.
    937         */
    938        public JPopupMenu getPopupMenu() {
    939            return this.popup;
    940        }
    941
    942        /**
    943         * Sets the popup menu for the panel.
    944         *
    945         * @param popup  the popup menu (<code>null</code> permitted).
    946         */
    947        public void setPopupMenu(JPopupMenu popup) {
    948            this.popup = popup;
    949        }
    950
    951        /**
    952         * Returns the chart rendering info from the most recent chart redraw.
    953         *
    954         * @return The chart rendering info.
    955         */
    956        public ChartRenderingInfo getChartRenderingInfo() {
    957            return this.info;
    958        }
    959
    960        /**
    961         * A convenience method that switches on mouse-based zooming.
    962         *
    963         * @param flag  <code>true</code> enables zooming and rectangle fill on
    964         *              zoom.
    965         */
    966        public void setMouseZoomable(boolean flag) {
    967            setMouseZoomable(flag, true);
    968        }
    969
    970        /**
    971         * A convenience method that switches on mouse-based zooming.
    972         *
    973         * @param flag  <code>true</code> if zooming enabled
    974         * @param fillRectangle  <code>true</code> if zoom rectangle is filled,
    975         *                       false if rectangle is shown as outline only.
    976         */
    977        public void setMouseZoomable(boolean flag, boolean fillRectangle) {
    978            setDomainZoomable(flag);
    979            setRangeZoomable(flag);
    980            setFillZoomRectangle(fillRectangle);
    981        }
    982
    983        /**
    984         * Returns the flag that determines whether or not zooming is enabled for
    985         * the domain axis.
    986         *
    987         * @return A boolean.
    988         */
    989        public boolean isDomainZoomable() {
    990            return this.domainZoomable;
    991        }
    992
    993        /**
    994         * Sets the flag that controls whether or not zooming is enable for the
    995         * domain axis.  A check is made to ensure that the current plot supports
    996         * zooming for the domain values.
    997         *
    998         * @param flag  <code>true</code> enables zooming if possible.
    999         */
    1000        public void setDomainZoomable(boolean flag) {
    1001            if (flag) {
    1002                Plot plot = this.chart.getPlot();
    1003                if (plot instanceof Zoomable) {
    1004                    Zoomable z = (Zoomable) plot;
    1005                    this.domainZoomable = flag && (z.isDomainZoomable());
    1006                }
    1007            }
    1008            else {
    1009                this.domainZoomable = false;
    1010            }
    1011        }
    1012
    1013        /**
    1014         * Returns the flag that determines whether or not zooming is enabled for
    1015         * the range axis.
    1016         *
    1017         * @return A boolean.
    1018         */
    1019        public boolean isRangeZoomable() {
    1020            return this.rangeZoomable;
    1021        }
    1022
    1023        /**
    1024         * A flag that controls mouse-based zooming on the vertical axis.
    1025         *
    1026         * @param flag  <code>true</code> enables zooming.
    1027         */
    1028        public void setRangeZoomable(boolean flag) {
    1029            if (flag) {
    1030                Plot plot = this.chart.getPlot();
    1031                if (plot instanceof Zoomable) {
    1032                    Zoomable z = (Zoomable) plot;
    1033                    this.rangeZoomable = flag && (z.isRangeZoomable());
    1034                }
    1035            }
    1036            else {
    1037                this.rangeZoomable = false;
    1038            }
    1039        }
    1040
    1041        /**
    1042         * Returns the flag that controls whether or not the zoom rectangle is
    1043         * filled when drawn.
    1044         *
    1045         * @return A boolean.
    1046         */
    1047        public boolean getFillZoomRectangle() {
    1048            return this.fillZoomRectangle;
    1049        }
    1050
    1051        /**
    1052         * A flag that controls how the zoom rectangle is drawn.
    1053         *
    1054         * @param flag  <code>true</code> instructs to fill the rectangle on
    1055         *              zoom, otherwise it will be outlined.
    1056         */
    1057        public void setFillZoomRectangle(boolean flag) {
    1058            this.fillZoomRectangle = flag;
    1059        }
    1060
    1061        /**
    1062         * Returns the zoom trigger distance.  This controls how far the mouse must
    1063         * move before a zoom action is triggered.
    1064         *
    1065         * @return The distance (in Java2D units).
    1066         */
    1067        public int getZoomTriggerDistance() {
    1068            return this.zoomTriggerDistance;
    1069        }
    1070
    1071        /**
    1072         * Sets the zoom trigger distance.  This controls how far the mouse must
    1073         * move before a zoom action is triggered.
    1074         *
    1075         * @param distance  the distance (in Java2D units).
    1076         */
    1077        public void setZoomTriggerDistance(int distance) {
    1078            this.zoomTriggerDistance = distance;
    1079        }
    1080
    1081        /**
    1082         * Returns the flag that controls whether or not a horizontal axis trace
    1083         * line is drawn over the plot area at the current mouse location.
    1084         *
    1085         * @return A boolean.
    1086         */
    1087        public boolean getHorizontalAxisTrace() {
    1088            return this.horizontalAxisTrace;
    1089        }
    1090
    1091        /**
    1092         * A flag that controls trace lines on the horizontal axis.
    1093         *
    1094         * @param flag  <code>true</code> enables trace lines for the mouse
    1095         *      pointer on the horizontal axis.
    1096         */
    1097        public void setHorizontalAxisTrace(boolean flag) {
    1098            this.horizontalAxisTrace = flag;
    1099        }
    1100
    1101        /**
    1102         * Returns the horizontal trace line.
    1103         *
    1104         * @return The horizontal trace line (possibly <code>null</code>).
    1105         */
    1106        protected Line2D getHorizontalTraceLine() {
    1107            return this.horizontalTraceLine;
    1108        }
    1109
    1110        /**
    1111         * Sets the horizontal trace line.
    1112         *
    1113         * @param line  the line (<code>null</code> permitted).
    1114         */
    1115        protected void setHorizontalTraceLine(Line2D line) {
    1116            this.horizontalTraceLine = line;
    1117        }
    1118
    1119        /**
    1120         * Returns the flag that controls whether or not a vertical axis trace
    1121         * line is drawn over the plot area at the current mouse location.
    1122         *
    1123         * @return A boolean.
    1124         */
    1125        public boolean getVerticalAxisTrace() {
    1126            return this.verticalAxisTrace;
    1127        }
    1128
    1129        /**
    1130         * A flag that controls trace lines on the vertical axis.
    1131         *
    1132         * @param flag  <code>true</code> enables trace lines for the mouse
    1133         *              pointer on the vertical axis.
    1134         */
    1135        public void setVerticalAxisTrace(boolean flag) {
    1136            this.verticalAxisTrace = flag;
    1137        }
    1138
    1139        /**
    1140         * Returns the vertical trace line.
    1141         *
    1142         * @return The vertical trace line (possibly <code>null</code>).
    1143         */
    1144        protected Line2D getVerticalTraceLine() {
    1145            return this.verticalTraceLine;
    1146        }
    1147
    1148        /**
    1149         * Sets the vertical trace line.
    1150         *
    1151         * @param line  the line (<code>null</code> permitted).
    1152         */
    1153        protected void setVerticalTraceLine(Line2D line) {
    1154            this.verticalTraceLine = line;
    1155        }
    1156
    1157        /**
    1158         * Returns the default directory for the "save as" option.
    1159         *
    1160         * @return The default directory (possibly <code>null</code>).
    1161         *
    1162         * @since 1.0.7
    1163         */
    1164        public File getDefaultDirectoryForSaveAs() {
    1165            return this.defaultDirectoryForSaveAs;
    1166        }
    1167
    1168        /**
    1169         * Sets the default directory for the "save as" option.  If you set this
    1170         * to <code>null</code>, the user's default directory will be used.
    1171         *
    1172         * @param directory  the directory (<code>null</code> permitted).
    1173         *
    1174         * @since 1.0.7
    1175         */
    1176        public void setDefaultDirectoryForSaveAs(File directory) {
    1177            if (directory != null) {
    1178                if (!directory.isDirectory()) {
    1179                    throw new IllegalArgumentException(
    1180                            "The 'directory' argument is not a directory.");
    1181                }
    1182            }
    1183            this.defaultDirectoryForSaveAs = directory;
    1184        }
    1185
    1186        /**
    1187         * Returns <code>true</code> if file extensions should be enforced, and
    1188         * <code>false</code> otherwise.
    1189         *
    1190         * @return The flag.
    1191         *
    1192         * @see #setEnforceFileExtensions(boolean)
    1193         */
    1194        public boolean isEnforceFileExtensions() {
    1195            return this.enforceFileExtensions;
    1196        }
    1197
    1198        /**
    1199         * Sets a flag that controls whether or not file extensions are enforced.
    1200         *
    1201         * @param enforce  the new flag value.
    1202         *
    1203         * @see #isEnforceFileExtensions()
    1204         */
    1205        public void setEnforceFileExtensions(boolean enforce) {
    1206            this.enforceFileExtensions = enforce;
    1207        }
    1208
    1209        /**
    1210         * Returns the flag that controls whether or not zoom operations are
    1211         * centered around the current anchor point.
    1212         *
    1213         * @return A boolean.
    1214         *
    1215         * @since 1.0.7
    1216         *
    1217         * @see #setZoomAroundAnchor(boolean)
    1218         */
    1219        public boolean getZoomAroundAnchor() {
    1220            return this.zoomAroundAnchor;
    1221        }
    1222
    1223        /**
    1224         * Sets the flag that controls whether or not zoom operations are
    1225         * centered around the current anchor point.
    1226         *
    1227         * @param zoomAroundAnchor  the new flag value.
    1228         *
    1229         * @since 1.0.7
    1230         *
    1231         * @see #getZoomAroundAnchor()
    1232         */
    1233        public void setZoomAroundAnchor(boolean zoomAroundAnchor) {
    1234            this.zoomAroundAnchor = zoomAroundAnchor;
    1235        }
    1236
    1237        /**
    1238         * Returns the zoom rectangle fill paint.
    1239         *
    1240         * @return The zoom rectangle fill paint (never <code>null</code>).
    1241         *
    1242         * @see #setZoomFillPaint(java.awt.Paint)
    1243         * @see #setFillZoomRectangle(boolean)
    1244         *
    1245         * @since 1.0.13
    1246         */
    1247        public Paint getZoomFillPaint() {
    1248            return this.zoomFillPaint;
    1249        }
    1250
    1251        /**
    1252         * Sets the zoom rectangle fill paint.
    1253         *
    1254         * @param paint  the paint (<code>null</code> not permitted).
    1255         *
    1256         * @see #getZoomFillPaint()
    1257         * @see #getFillZoomRectangle()
    1258         *
    1259         * @since 1.0.13
    1260         */
    1261        public void setZoomFillPaint(Paint paint) {
    1262            if (paint == null) {
    1263                throw new IllegalArgumentException("Null 'paint' argument.");
    1264            }
    1265            this.zoomFillPaint = paint;
    1266        }
    1267
    1268        /**
    1269         * Returns the zoom rectangle outline paint.
    1270         *
    1271         * @return The zoom rectangle outline paint (never <code>null</code>).
    1272         *
    1273         * @see #setZoomOutlinePaint(java.awt.Paint)
    1274         * @see #setFillZoomRectangle(boolean)
    1275         *
    1276         * @since 1.0.13
    1277         */
    1278        public Paint getZoomOutlinePaint() {
    1279            return this.zoomOutlinePaint;
    1280        }
    1281
    1282        /**
    1283         * Sets the zoom rectangle outline paint.
    1284         *
    1285         * @param paint  the paint (<code>null</code> not permitted).
    1286         *
    1287         * @see #getZoomOutlinePaint()
    1288         * @see #getFillZoomRectangle()
    1289         *
    1290         * @since 1.0.13
    1291         */
    1292        public void setZoomOutlinePaint(Paint paint) {
    1293            this.zoomOutlinePaint = paint;
    1294        }
    1295
    1296        /**
    1297         * The mouse wheel handler.  This will be an instance of MouseWheelHandler
    1298         * but we can't reference that class directly because it depends on JRE 1.4
    1299         * and we still want to support JRE 1.3.1.
    1300         */
    1301        private Object mouseWheelHandler;
    1302
    1303        /**
    1304         * Returns <code>true</code> if the mouse wheel handler is enabled, and
    1305         * <code>false</code> otherwise.
    1306         *
    1307         * @return A boolean.
    1308         *
    1309         * @since 1.0.13
    1310         */
    1311        public boolean isMouseWheelEnabled() {
    1312            return this.mouseWheelHandler != null;
    1313        }
    1314
    1315        /**
    1316         * Enables or disables mouse wheel support for the panel.
    1317         * Note that this method does nothing when running JFreeChart on JRE 1.3.1,
    1318         * because that older version of the Java runtime does not support
    1319         * mouse wheel events.
    1320         *
    1321         * @param flag  a boolean.
    1322         *
    1323         * @since 1.0.13
    1324         */
    1325        public void setMouseWheelEnabled(boolean flag) {
    1326            if (flag && this.mouseWheelHandler == null) {
    1327                // use reflection to instantiate a mouseWheelHandler because to
    1328                // continue supporting JRE 1.3.1 we cannot depend on the
    1329                // MouseWheelListener interface directly
    1330                try {
    1331                    Class c = Class.forName("org.jfree.chart.MouseWheelHandler");
    1332                    Constructor cc = c.getConstructor(new Class[] {
    1333                            ChartPanel.class});
    1334                    Object mwh = cc.newInstance(new Object[] {this});
    1335                    this.mouseWheelHandler = mwh;
    1336                }
    1337                catch (ClassNotFoundException e) {
    1338                    // the class isn't there, so we must have compiled JFreeChart
    1339                    // with JDK 1.3.1 - thus, we can't have mouse wheel support
    1340                }
    1341                catch (SecurityException e) {
    1342                    e.printStackTrace();
    1343                }
    1344                catch (NoSuchMethodException e) {
    1345                    e.printStackTrace();
    1346                }
    1347                catch (IllegalArgumentException e) {
    1348                    e.printStackTrace();
    1349                }
    1350                catch (InstantiationException e) {
    1351                    e.printStackTrace();
    1352                }
    1353                catch (IllegalAccessException e) {
    1354                    e.printStackTrace();
    1355                }
    1356                catch (InvocationTargetException e) {
    1357                    e.printStackTrace();
    1358                }
    1359            }
    1360            else {
    1361
    1362                if (this.mouseWheelHandler != null) {
    1363                    // use reflection to deregister the mouseWheelHandler
    1364                    try {
    1365                        Class mwl = Class.forName(
    1366                                "java.awt.event.MouseWheelListener");
    1367                        Class c2 = ChartPanel.class;
    1368                        Method m = c2.getMethod("removeMouseWheelListener",
    1369                                new Class[] {mwl});
    1370                        m.invoke(this, new Object[] {this.mouseWheelHandler});
    1371                    }
    1372                    catch (ClassNotFoundException e) {
    1373                        // must be running on JRE 1.3.1, so just ignore this
    1374                    }
    1375                    catch (SecurityException e) {
    1376                        e.printStackTrace();
    1377                    }
    1378                    catch (NoSuchMethodException e) {
    1379                        e.printStackTrace();
    1380                    }
    1381                    catch (IllegalArgumentException e) {
    1382                        e.printStackTrace();
    1383                    }
    1384                    catch (IllegalAccessException e) {
    1385                        e.printStackTrace();
    1386                    }
    1387                    catch (InvocationTargetException e) {
    1388                        e.printStackTrace();
    1389                    }
    1390                }
    1391            }
    1392        }
    1393
    1394        /**
    1395         * Add an overlay to the panel.
    1396         *
    1397         * @param overlay  the overlay (<code>null</code> not permitted).
    1398         *
    1399         * @since 1.0.13
    1400         */
    1401        public void addOverlay(Overlay overlay) {
    1402            if (overlay == null) {
    1403                throw new IllegalArgumentException("Null 'overlay' argument.");
    1404            }
    1405            this.overlays.add(overlay);
    1406            overlay.addChangeListener(this);
    1407            repaint();
    1408        }
    1409
    1410        /**
    1411         * Removes an overlay from the panel.
    1412         *
    1413         * @param overlay  the overlay to remove (<code>null</code> not permitted).
    1414         *
    1415         * @since 1.0.13
    1416         */
    1417        public void removeOverlay(Overlay overlay) {
    1418            if (overlay == null) {
    1419                throw new IllegalArgumentException("Null 'overlay' argument.");
    1420            }
    1421            boolean removed = this.overlays.remove(overlay);
    1422            if (removed) {
    1423                overlay.removeChangeListener(this);
    1424                repaint();
    1425            }
    1426        }
    1427
    1428        /**
    1429         * Handles a change to an overlay by repainting the panel.
    1430         *
    1431         * @param event  the event.
    1432         *
    1433         * @since 1.0.13
    1434         */
    1435        public void overlayChanged(OverlayChangeEvent event) {
    1436            repaint();
    1437        }
    1438
    1439        /**
    1440         * Switches the display of tooltips for the panel on or off.  Note that
    1441         * tooltips can only be displayed if the chart has been configured to
    1442         * generate tooltip items.
    1443         *
    1444         * @param flag  <code>true</code> to enable tooltips, <code>false</code> to
    1445         *              disable tooltips.
    1446         */
    1447        public void setDisplayToolTips(boolean flag) {
    1448            if (flag) {
    1449                ToolTipManager.sharedInstance().registerComponent(this);
    1450            }
    1451            else {
    1452                ToolTipManager.sharedInstance().unregisterComponent(this);
    1453            }
    1454        }
    1455
    1456        /**
    1457         * Returns a string for the tooltip.
    1458         *
    1459         * @param e  the mouse event.
    1460         *
    1461         * @return A tool tip or <code>null</code> if no tooltip is available.
    1462         */
    1463        public String getToolTipText(MouseEvent e) {
    1464
    1465            String result = null;
    1466            if (this.info != null) {
    1467                EntityCollection entities = this.info.getEntityCollection();
    1468                if (entities != null) {
    1469                    Insets insets = getInsets();
    1470                    ChartEntity entity = entities.getEntity(
    1471                            (int) ((e.getX() - insets.left) / this.scaleX),
    1472                            (int) ((e.getY() - insets.top) / this.scaleY));
    1473                    if (entity != null) {
    1474                        result = entity.getToolTipText();
    1475                    }
    1476                }
    1477            }
    1478            return result;
    1479
    1480        }
    1481
    1482        /**
    1483         * Translates a Java2D point on the chart to a screen location.
    1484         *
    1485         * @param java2DPoint  the Java2D point.
    1486         *
    1487         * @return The screen location.
    1488         */
    1489        public Point translateJava2DToScreen(Point2D java2DPoint) {
    1490            Insets insets = getInsets();
    1491            int x = (int) (java2DPoint.getX() * this.scaleX + insets.left);
    1492            int y = (int) (java2DPoint.getY() * this.scaleY + insets.top);
    1493            return new Point(x, y);
    1494        }
    1495
    1496        /**
    1497         * Translates a panel (component) location to a Java2D point.
    1498         *
    1499         * @param screenPoint  the screen location (<code>null</code> not
    1500         *                     permitted).
    1501         *
    1502         * @return The Java2D coordinates.
    1503         */
    1504        public Point2D translateScreenToJava2D(Point screenPoint) {
    1505            Insets insets = getInsets();
    1506            double x = (screenPoint.getX() - insets.left) / this.scaleX;
    1507            double y = (screenPoint.getY() - insets.top) / this.scaleY;
    1508            return new Point2D.Double(x, y);
    1509        }
    1510
    1511        /**
    1512         * Applies any scaling that is in effect for the chart drawing to the
    1513         * given rectangle.
    1514         *
    1515         * @param rect  the rectangle (<code>null</code> not permitted).
    1516         *
    1517         * @return A new scaled rectangle.
    1518         */
    1519        public Rectangle2D scale(Rectangle2D rect) {
    1520            Insets insets = getInsets();
    1521            double x = rect.getX() * getScaleX() + insets.left;
    1522            double y = rect.getY() * getScaleY() + insets.top;
    1523            double w = rect.getWidth() * getScaleX();
    1524            double h = rect.getHeight() * getScaleY();
    1525            return new Rectangle2D.Double(x, y, w, h);
    1526        }
    1527
    1528        /**
    1529         * Returns the chart entity at a given point.
    1530         * <P>
    1531         * This method will return null if there is (a) no entity at the given
    1532         * point, or (b) no entity collection has been generated.
    1533         *
    1534         * @param viewX  the x-coordinate.
    1535         * @param viewY  the y-coordinate.
    1536         *
    1537         * @return The chart entity (possibly <code>null</code>).
    1538         */
    1539        public ChartEntity getEntityForPoint(int viewX, int viewY) {
    1540
    1541            ChartEntity result = null;
    1542            if (this.info != null) {
    1543                Insets insets = getInsets();
    1544                double x = (viewX - insets.left) / this.scaleX;
    1545                double y = (viewY - insets.top) / this.scaleY;
    1546                EntityCollection entities = this.info.getEntityCollection();
    1547                result = entities != null ? entities.getEntity(x, y) : null;
    1548            }
    1549            return result;
    1550
    1551        }
    1552
    1553        /**
    1554         * Returns the flag that controls whether or not the offscreen buffer
    1555         * needs to be refreshed.
    1556         *
    1557         * @return A boolean.
    1558         */
    1559        public boolean getRefreshBuffer() {
    1560            return this.refreshBuffer;
    1561        }
    1562
    1563        /**
    1564         * Sets the refresh buffer flag.  This flag is used to avoid unnecessary
    1565         * redrawing of the chart when the offscreen image buffer is used.
    1566         *
    1567         * @param flag  <code>true</code> indicates that the buffer should be
    1568         *              refreshed.
    1569         */
    1570        public void setRefreshBuffer(boolean flag) {
    1571            this.refreshBuffer = flag;
    1572        }
    1573
    1574        /**
    1575         * Paints the component by drawing the chart to fill the entire component,
    1576         * but allowing for the insets (which will be non-zero if a border has been
    1577         * set for this component).  To increase performance (at the expense of
    1578         * memory), an off-screen buffer image can be used.
    1579         *
    1580         * @param g  the graphics device for drawing on.
    1581         */
    1582        public void paintComponent(Graphics g) {
    1583            super.paintComponent(g);
    1584            if (this.chart == null) {
    1585                return;
    1586            }
    1587            Graphics2D g2 = (Graphics2D) g.create();
    1588
    1589            // first determine the size of the chart rendering area...
    1590            Dimension size = getSize();
    1591            Insets insets = getInsets();
    1592            Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top,
    1593                    size.getWidth() - insets.left - insets.right,
    1594                    size.getHeight() - insets.top - insets.bottom);
    1595
    1596            // work out if scaling is required...
    1597            boolean scale = false;
    1598            double drawWidth = available.getWidth();
    1599            double drawHeight = available.getHeight();
    1600            this.scaleX = 1.0;
    1601            this.scaleY = 1.0;
    1602
    1603            if (drawWidth < this.minimumDrawWidth) {
    1604                this.scaleX = drawWidth / this.minimumDrawWidth;
    1605                drawWidth = this.minimumDrawWidth;
    1606                scale = true;
    1607            }
    1608            else if (drawWidth > this.maximumDrawWidth) {
    1609                this.scaleX = drawWidth / this.maximumDrawWidth;
    1610                drawWidth = this.maximumDrawWidth;
    1611                scale = true;
    1612            }
    1613
    1614            if (drawHeight < this.minimumDrawHeight) {
    1615                this.scaleY = drawHeight / this.minimumDrawHeight;
    1616                drawHeight = this.minimumDrawHeight;
    1617                scale = true;
    1618            }
    1619            else if (drawHeight > this.maximumDrawHeight) {
    1620                this.scaleY = drawHeight / this.maximumDrawHeight;
    1621                drawHeight = this.maximumDrawHeight;
    1622                scale = true;
    1623            }
    1624
    1625            Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth,
    1626                    drawHeight);
    1627
    1628            // are we using the chart buffer?
    1629            if (this.useBuffer) {
    1630
    1631                // do we need to resize the buffer?
    1632                if ((this.chartBuffer == null)
    1633                        || (this.chartBufferWidth != available.getWidth())
    1634                        || (this.chartBufferHeight != available.getHeight())) {
    1635                    this.chartBufferWidth = (int) available.getWidth();
    1636                    this.chartBufferHeight = (int) available.getHeight();
    1637                    GraphicsConfiguration gc = g2.getDeviceConfiguration();
    1638                    this.chartBuffer = gc.createCompatibleImage(
    1639                            this.chartBufferWidth, this.chartBufferHeight,
    1640                            Transparency.TRANSLUCENT);
    1641                    this.refreshBuffer = true;
    1642                }
    1643
    1644                // do we need to redraw the buffer?
    1645                if (this.refreshBuffer) {
    1646
    1647                    this.refreshBuffer = false; // clear the flag
    1648
    1649                    Rectangle2D bufferArea = new Rectangle2D.Double(
    1650                            0, 0, this.chartBufferWidth, this.chartBufferHeight);
    1651
    1652                    Graphics2D bufferG2 = (Graphics2D)
    1653                            this.chartBuffer.getGraphics();
    1654                    Rectangle r = new Rectangle(0, 0, this.chartBufferWidth,
    1655                            this.chartBufferHeight);
    1656                    bufferG2.setPaint(getBackground());
    1657                    bufferG2.fill(r);
    1658                    if (scale) {
    1659                        AffineTransform saved = bufferG2.getTransform();
    1660                        AffineTransform st = AffineTransform.getScaleInstance(
    1661                                this.scaleX, this.scaleY);
    1662                        bufferG2.transform(st);
    1663                        this.chart.draw(bufferG2, chartArea, this.anchor,
    1664                                this.info);
    1665                        bufferG2.setTransform(saved);
    1666                    }
    1667                    else {
    1668                        this.chart.draw(bufferG2, bufferArea, this.anchor,
    1669                                this.info);
    1670                    }
    1671
    1672                }
    1673
    1674                // zap the buffer onto the panel...
    1675                g2.drawImage(this.chartBuffer, insets.left, insets.top, this);
    1676
    1677            }
    1678
    1679            // or redrawing the chart every time...
    1680            else {
    1681
    1682                AffineTransform saved = g2.getTransform();
    1683                g2.translate(insets.left, insets.top);
    1684                if (scale) {
    1685                    AffineTransform st = AffineTransform.getScaleInstance(
    1686                            this.scaleX, this.scaleY);
    1687                    g2.transform(st);
    1688                }
    1689                this.chart.draw(g2, chartArea, this.anchor, this.info);
    1690                g2.setTransform(saved);
    1691
    1692            }
    1693
    1694            Iterator iterator = this.overlays.iterator();
    1695            while (iterator.hasNext()) {
    1696                Overlay overlay = (Overlay) iterator.next();
    1697                overlay.paintOverlay(g2, this);
    1698            }
    1699
    1700            // redraw the zoom rectangle (if present) - if useBuffer is false,
    1701            // we use XOR so we can XOR the rectangle away again without redrawing
    1702            // the chart
    1703            drawZoomRectangle(g2, !this.useBuffer);
    1704
    1705            g2.dispose();
    1706
    1707            this.anchor = null;
    1708            this.verticalTraceLine = null;
    1709            this.horizontalTraceLine = null;
    1710
    1711        }
    1712
    1713        /**
    1714         * Receives notification of changes to the chart, and redraws the chart.
    1715         *
    1716         * @param event  details of the chart change event.
    1717         */
    1718        public void chartChanged(ChartChangeEvent event) {
    1719            this.refreshBuffer = true;
    1720            Plot plot = this.chart.getPlot();
    1721            if (plot instanceof Zoomable) {
    1722                Zoomable z = (Zoomable) plot;
    1723                this.orientation = z.getOrientation();
    1724            }
    1725            repaint();
    1726        }
    1727
    1728        /**
    1729         * Receives notification of a chart progress event.
    1730         *
    1731         * @param event  the event.
    1732         */
    1733        public void chartProgress(ChartProgressEvent event) {
    1734            // does nothing - override if necessary
    1735        }
    1736
    1737        /**
    1738         * Handles action events generated by the popup menu.
    1739         *
    1740         * @param event  the event.
    1741         */
    1742        public void actionPerformed(ActionEvent event) {
    1743
    1744            String command = event.getActionCommand();
    1745
    1746            // many of the zoom methods need a screen location - all we have is
    1747            // the zoomPoint, but it might be null.  Here we grab the x and y
    1748            // coordinates, or use defaults...
    1749            double screenX = -1.0;
    1750            double screenY = -1.0;
    1751            if (this.zoomPoint != null) {
    1752                screenX = this.zoomPoint.getX();
    1753                screenY = this.zoomPoint.getY();
    1754            }
    1755
    1756            if (command.equals(PROPERTIES_COMMAND)) {
    1757                doEditChartProperties();
    1758            }
    1759            else if (command.equals(COPY_COMMAND)) {
    1760                doCopy();
    1761            }
    1762            else if (command.equals(SAVE_COMMAND)) {
    1763                try {
    1764                    doSaveAs();
    1765                }
    1766                catch (IOException e) {
    1767                    e.printStackTrace();
    1768                }
    1769            }
    1770            else if (command.equals(PRINT_COMMAND)) {
    1771                createChartPrintJob();
    1772            }
    1773            else if (command.equals(ZOOM_IN_BOTH_COMMAND)) {
    1774                zoomInBoth(screenX, screenY);
    1775            }
    1776            else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) {
    1777                zoomInDomain(screenX, screenY);
    1778            }
    1779            else if (command.equals(ZOOM_IN_RANGE_COMMAND)) {
    1780                zoomInRange(screenX, screenY);
    1781            }
    1782            else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) {
    1783                zoomOutBoth(screenX, screenY);
    1784            }
    1785            else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) {
    1786                zoomOutDomain(screenX, screenY);
    1787            }
    1788            else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) {
    1789                zoomOutRange(screenX, screenY);
    1790            }
    1791            else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) {
    1792                restoreAutoBounds();
    1793            }
    1794            else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) {
    1795                restoreAutoDomainBounds();
    1796            }
    1797            else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) {
    1798                restoreAutoRangeBounds();
    1799            }
    1800
    1801        }
    1802
    1803        /**
    1804         * Handles a 'mouse entered' event. This method changes the tooltip delays
    1805         * of ToolTipManager.sharedInstance() to the possibly different values set
    1806         * for this chart panel.
    1807         *
    1808         * @param e  the mouse event.
    1809         */
    1810        public void mouseEntered(MouseEvent e) {
    1811            if (!this.ownToolTipDelaysActive) {
    1812                ToolTipManager ttm = ToolTipManager.sharedInstance();
    1813
    1814                this.originalToolTipInitialDelay = ttm.getInitialDelay();
    1815                ttm.setInitialDelay(this.ownToolTipInitialDelay);
    1816
    1817                this.originalToolTipReshowDelay = ttm.getReshowDelay();
    1818                ttm.setReshowDelay(this.ownToolTipReshowDelay);
    1819
    1820                this.originalToolTipDismissDelay = ttm.getDismissDelay();
    1821                ttm.setDismissDelay(this.ownToolTipDismissDelay);
    1822
    1823                this.ownToolTipDelaysActive = true;
    1824            }
    1825        }
    1826
    1827        /**
    1828         * Handles a 'mouse exited' event. This method resets the tooltip delays of
    1829         * ToolTipManager.sharedInstance() to their
    1830         * original values in effect before mouseEntered()
    1831         *
    1832         * @param e  the mouse event.
    1833         */
    1834        public void mouseExited(MouseEvent e) {
    1835            if (this.ownToolTipDelaysActive) {
    1836                // restore original tooltip dealys
    1837                ToolTipManager ttm = ToolTipManager.sharedInstance();
    1838                ttm.setInitialDelay(this.originalToolTipInitialDelay);
    1839                ttm.setReshowDelay(this.originalToolTipReshowDelay);
    1840                ttm.setDismissDelay(this.originalToolTipDismissDelay);
    1841                this.ownToolTipDelaysActive = false;
    1842            }
    1843        }
    1844
    1845        /**
    1846         * Handles a 'mouse pressed' event.
    1847         * <P>
    1848         * This event is the popup trigger on Unix/Linux.  For Windows, the popup
    1849         * trigger is the 'mouse released' event.
    1850         *
    1851         * @param e  The mouse event.
    1852         */
    1853        public void mousePressed(MouseEvent e) {
    1854            Plot plot = this.chart.getPlot();
    1855            int mods = e.getModifiers();
    1856            if ((mods & this.panMask) == this.panMask) {
    1857                // can we pan this plot?
    1858                if (plot instanceof Pannable) {
    1859                    Pannable pannable = (Pannable) plot;
    1860                    if (pannable.isDomainPannable() || pannable.isRangePannable()) {
    1861                        Rectangle2D screenDataArea = getScreenDataArea(e.getX(),
    1862                                e.getY());
    1863                        if (screenDataArea != null && screenDataArea.contains(
    1864                                e.getPoint())) {
    1865                            this.panW = screenDataArea.getWidth();
    1866                            this.panH = screenDataArea.getHeight();
    1867                            this.panLast = e.getPoint();
    1868                            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    1869                        }
    1870                    }
    1871                    // the actual panning occurs later in the mouseDragged()
    1872                    // method
    1873                }
    1874            }
    1875            else if (this.zoomRectangle == null) {
    1876                Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
    1877                if (screenDataArea != null) {
    1878                    this.zoomPoint = getPointInRectangle(e.getX(), e.getY(),
    1879                            screenDataArea);
    1880                }
    1881                else {
    1882                    this.zoomPoint = null;
    1883                }
    1884                if (e.isPopupTrigger()) {
    1885                    if (this.popup != null) {
    1886                        displayPopupMenu(e.getX(), e.getY());
    1887                    }
    1888                }
    1889            }
    1890        }
    1891
    1892        /**
    1893         * Returns a point based on (x, y) but constrained to be within the bounds
    1894         * of the given rectangle.  This method could be moved to JCommon.
    1895         *
    1896         * @param x  the x-coordinate.
    1897         * @param y  the y-coordinate.
    1898         * @param area  the rectangle (<code>null</code> not permitted).
    1899         *
    1900         * @return A point within the rectangle.
    1901         */
    1902        private Point2D getPointInRectangle(int x, int y, Rectangle2D area) {
    1903            double xx = Math.max(area.getMinX(), Math.min(x, area.getMaxX()));
    1904            double yy = Math.max(area.getMinY(), Math.min(y, area.getMaxY()));
    1905            return new Point2D.Double(xx, yy);
    1906        }
    1907
    1908        /**
    1909         * Handles a 'mouse dragged' event.
    1910         *
    1911         * @param e  the mouse event.
    1912         */
    1913        public void mouseDragged(MouseEvent e) {
    1914
    1915            // if the popup menu has already been triggered, then ignore dragging...
    1916            if (this.popup != null && this.popup.isShowing()) {
    1917                return;
    1918            }
    1919
    1920            // handle panning if we have a start point
    1921            if (this.panLast != null) {
    1922                double dx = e.getX() - this.panLast.getX();
    1923                double dy = e.getY() - this.panLast.getY();
    1924                if (dx == 0.0 && dy == 0.0) {
    1925                    return;
    1926                }
    1927                double wPercent = -dx / this.panW;
    1928                double hPercent = dy / this.panH;
    1929                boolean old = this.chart.getPlot().isNotify();
    1930                this.chart.getPlot().setNotify(false);
    1931                Pannable p = (Pannable) this.chart.getPlot();
    1932                if (p.getOrientation() == PlotOrientation.VERTICAL) {
    1933                    p.panDomainAxes(wPercent, this.info.getPlotInfo(),
    1934                            this.panLast);
    1935                    p.panRangeAxes(hPercent, this.info.getPlotInfo(),
    1936                            this.panLast);
    1937                }
    1938                else {
    1939                    p.panDomainAxes(hPercent, this.info.getPlotInfo(),
    1940                            this.panLast);
    1941                    p.panRangeAxes(wPercent, this.info.getPlotInfo(),
    1942                            this.panLast);
    1943                }
    1944                this.panLast = e.getPoint();
    1945                this.chart.getPlot().setNotify(old);
    1946                return;
    1947            }
    1948
    1949            // if no initial zoom point was set, ignore dragging...
    1950            if (this.zoomPoint == null) {
    1951                return;
    1952            }
    1953            Graphics2D g2 = (Graphics2D) getGraphics();
    1954
    1955            // erase the previous zoom rectangle (if any).  We only need to do
    1956            // this is we are using XOR mode, which we do when we're not using
    1957            // the buffer (if there is a buffer, then at the end of this method we
    1958            // just trigger a repaint)
    1959            if (!this.useBuffer) {
    1960                drawZoomRectangle(g2, true);
    1961            }
    1962
    1963            boolean hZoom = false;
    1964            boolean vZoom = false;
    1965            if (this.orientation == PlotOrientation.HORIZONTAL) {
    1966                hZoom = this.rangeZoomable;
    1967                vZoom = this.domainZoomable;
    1968            }
    1969            else {
    1970                hZoom = this.domainZoomable;
    1971                vZoom = this.rangeZoomable;
    1972            }
    1973            Rectangle2D scaledDataArea = getScreenDataArea(
    1974                    (int) this.zoomPoint.getX(), (int) this.zoomPoint.getY());
    1975            if (hZoom && vZoom) {
    1976                // selected rectangle shouldn't extend outside the data area...
    1977                double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
    1978                double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
    1979                this.zoomRectangle = new Rectangle2D.Double(
    1980                        this.zoomPoint.getX(), this.zoomPoint.getY(),
    1981                        xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY());
    1982            }
    1983            else if (hZoom) {
    1984                double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
    1985                this.zoomRectangle = new Rectangle2D.Double(
    1986                        this.zoomPoint.getX(), scaledDataArea.getMinY(),
    1987                        xmax - this.zoomPoint.getX(), scaledDataArea.getHeight());
    1988            }
    1989            else if (vZoom) {
    1990                double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
    1991                this.zoomRectangle = new Rectangle2D.Double(
    1992                        scaledDataArea.getMinX(), this.zoomPoint.getY(),
    1993                        scaledDataArea.getWidth(), ymax - this.zoomPoint.getY());
    1994            }
    1995
    1996            // Draw the new zoom rectangle...
    1997            if (this.useBuffer) {
    1998                repaint();
    1999            }
    2000            else {
    2001                // with no buffer, we use XOR to draw the rectangle "over" the
    2002                // chart...
    2003                drawZoomRectangle(g2, true);
    2004            }
    2005            g2.dispose();
    2006
    2007        }
    2008
    2009        /**
    2010         * Handles a 'mouse released' event.  On Windows, we need to check if this
    2011         * is a popup trigger, but only if we haven't already been tracking a zoom
    2012         * rectangle.
    2013         *
    2014         * @param e  information about the event.
    2015         */
    2016        public void mouseReleased(MouseEvent e) {
    2017
    2018            // if we've been panning, we need to reset now that the mouse is
    2019            // released...
    2020            if (this.panLast != null) {
    2021                this.panLast = null;
    2022                setCursor(Cursor.getDefaultCursor());
    2023            }
    2024
    2025            else if (this.zoomRectangle != null) {
    2026                boolean hZoom = false;
    2027                boolean vZoom = false;
    2028                if (this.orientation == PlotOrientation.HORIZONTAL) {
    2029                    hZoom = this.rangeZoomable;
    2030                    vZoom = this.domainZoomable;
    2031                }
    2032                else {
    2033                    hZoom = this.domainZoomable;
    2034                    vZoom = this.rangeZoomable;
    2035                }
    2036
    2037                boolean zoomTrigger1 = hZoom && Math.abs(e.getX()
    2038                    - this.zoomPoint.getX()) >= this.zoomTriggerDistance;
    2039                boolean zoomTrigger2 = vZoom && Math.abs(e.getY()
    2040                    - this.zoomPoint.getY()) >= this.zoomTriggerDistance;
    2041                if (zoomTrigger1 || zoomTrigger2) {
    2042                    if ((hZoom && (e.getX() < this.zoomPoint.getX()))
    2043                        || (vZoom && (e.getY() < this.zoomPoint.getY()))) {
    2044                        restoreAutoBounds();
    2045                    }
    2046                    else {
    2047                        double x, y, w, h;
    2048                        Rectangle2D screenDataArea = getScreenDataArea(
    2049                                (int) this.zoomPoint.getX(),
    2050                                (int) this.zoomPoint.getY());
    2051                        double maxX = screenDataArea.getMaxX();
    2052                        double maxY = screenDataArea.getMaxY();
    2053                        // for mouseReleased event, (horizontalZoom || verticalZoom)
    2054                        // will be true, so we can just test for either being false;
    2055                        // otherwise both are true
    2056                        if (!vZoom) {
    2057                            x = this.zoomPoint.getX();
    2058                            y = screenDataArea.getMinY();
    2059                            w = Math.min(this.zoomRectangle.getWidth(),
    2060                                    maxX - this.zoomPoint.getX());
    2061                            h = screenDataArea.getHeight();
    2062                        }
    2063                        else if (!hZoom) {
    2064                            x = screenDataArea.getMinX();
    2065                            y = this.zoomPoint.getY();
    2066                            w = screenDataArea.getWidth();
    2067                            h = Math.min(this.zoomRectangle.getHeight(),
    2068                                    maxY - this.zoomPoint.getY());
    2069                        }
    2070                        else {
    2071                            x = this.zoomPoint.getX();
    2072                            y = this.zoomPoint.getY();
    2073                            w = Math.min(this.zoomRectangle.getWidth(),
    2074                                    maxX - this.zoomPoint.getX());
    2075                            h = Math.min(this.zoomRectangle.getHeight(),
    2076                                    maxY - this.zoomPoint.getY());
    2077                        }
    2078                        Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
    2079                        zoom(zoomArea);
    2080                    }
    2081                    this.zoomPoint = null;
    2082                    this.zoomRectangle = null;
    2083                }
    2084                else {
    2085                    // erase the zoom rectangle
    2086                    Graphics2D g2 = (Graphics2D) getGraphics();
    2087                    if (this.useBuffer) {
    2088                        repaint();
    2089                    }
    2090                    else {
    2091                        drawZoomRectangle(g2, true);
    2092                    }
    2093                    g2.dispose();
    2094                    this.zoomPoint = null;
    2095                    this.zoomRectangle = null;
    2096                }
    2097
    2098            }
    2099
    2100            else if (e.isPopupTrigger()) {
    2101                if (this.popup != null) {
    2102                    displayPopupMenu(e.getX(), e.getY());
    2103                }
    2104            }
    2105
    2106        }
    2107
    2108        /**
    2109         * Receives notification of mouse clicks on the panel. These are
    2110         * translated and passed on to any registered {@link ChartMouseListener}s.
    2111         *
    2112         * @param event  Information about the mouse event.
    2113         */
    2114        public void mouseClicked(MouseEvent event) {
    2115
    2116            Insets insets = getInsets();
    2117            int x = (int) ((event.getX() - insets.left) / this.scaleX);
    2118            int y = (int) ((event.getY() - insets.top) / this.scaleY);
    2119
    2120            this.anchor = new Point2D.Double(x, y);
    2121            if (this.chart == null) {
    2122                return;
    2123            }
    2124            this.chart.setNotify(true);  // force a redraw
    2125            // new entity code...
    2126            Object[] listeners = this.chartMouseListeners.getListeners(
    2127                    ChartMouseListener.class);
    2128            if (listeners.length == 0) {
    2129                return;
    2130            }
    2131
    2132            ChartEntity entity = null;
    2133            if (this.info != null) {
    2134                EntityCollection entities = this.info.getEntityCollection();
    2135                if (entities != null) {
    2136                    entity = entities.getEntity(x, y);
    2137                }
    2138            }
    2139            ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event,
    2140                    entity);
    2141            for (int i = listeners.length - 1; i >= 0; i -= 1) {
    2142                ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent);
    2143            }
    2144
    2145        }
    2146
    2147        /**
    2148         * Implementation of the MouseMotionListener's method.
    2149         *
    2150         * @param e  the event.
    2151         */
    2152        public void mouseMoved(MouseEvent e) {
    2153            Graphics2D g2 = (Graphics2D) getGraphics();
    2154            if (this.horizontalAxisTrace) {
    2155                drawHorizontalAxisTrace(g2, e.getX());
    2156            }
    2157            if (this.verticalAxisTrace) {
    2158                drawVerticalAxisTrace(g2, e.getY());
    2159            }
    2160            g2.dispose();
    2161
    2162            Object[] listeners = this.chartMouseListeners.getListeners(
    2163                    ChartMouseListener.class);
    2164            if (listeners.length == 0) {
    2165                return;
    2166            }
    2167            Insets insets = getInsets();
    2168            int x = (int) ((e.getX() - insets.left) / this.scaleX);
    2169            int y = (int) ((e.getY() - insets.top) / this.scaleY);
    2170
    2171            ChartEntity entity = null;
    2172            if (this.info != null) {
    2173                EntityCollection entities = this.info.getEntityCollection();
    2174                if (entities != null) {
    2175                    entity = entities.getEntity(x, y);
    2176                }
    2177            }
    2178
    2179            // we can only generate events if the panel's chart is not null
    2180            // (see bug report 1556951)
    2181            if (this.chart != null) {
    2182                ChartMouseEvent event = new ChartMouseEvent(getChart(), e, entity);
    2183                for (int i = listeners.length - 1; i >= 0; i -= 1) {
    2184                    ((ChartMouseListener) listeners[i]).chartMouseMoved(event);
    2185                }
    2186            }
    2187
    2188        }
    2189
    2190        /**
    2191         * Zooms in on an anchor point (specified in screen coordinate space).
    2192         *
    2193         * @param x  the x value (in screen coordinates).
    2194         * @param y  the y value (in screen coordinates).
    2195         */
    2196        public void zoomInBoth(double x, double y) {
    2197            Plot plot = this.chart.getPlot();
    2198            if (plot == null) {
    2199                return;
    2200            }
    2201            // here we tweak the notify flag on the plot so that only
    2202            // one notification happens even though we update multiple
    2203            // axes...
    2204            boolean savedNotify = plot.isNotify();
    2205            plot.setNotify(false);
    2206            zoomInDomain(x, y);
    2207            zoomInRange(x, y);
    2208            plot.setNotify(savedNotify);
    2209        }
    2210
    2211        /**
    2212         * Decreases the length of the domain axis, centered about the given
    2213         * coordinate on the screen.  The length of the domain axis is reduced
    2214         * by the value of {@link #getZoomInFactor()}.
    2215         *
    2216         * @param x  the x coordinate (in screen coordinates).
    2217         * @param y  the y-coordinate (in screen coordinates).
    2218         */
    2219        public void zoomInDomain(double x, double y) {
    2220            Plot plot = this.chart.getPlot();
    2221            if (plot instanceof Zoomable) {
    2222                // here we tweak the notify flag on the plot so that only
    2223                // one notification happens even though we update multiple
    2224                // axes...
    2225                boolean savedNotify = plot.isNotify();
    2226                plot.setNotify(false);
    2227                Zoomable z = (Zoomable) plot;
    2228                z.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(),
    2229                        translateScreenToJava2D(new Point((int) x, (int) y)),
    2230                        this.zoomAroundAnchor);
    2231                plot.setNotify(savedNotify);
    2232            }
    2233        }
    2234
    2235        /**
    2236         * Decreases the length of the range axis, centered about the given
    2237         * coordinate on the screen.  The length of the range axis is reduced by
    2238         * the value of {@link #getZoomInFactor()}.
    2239         *
    2240         * @param x  the x-coordinate (in screen coordinates).
    2241         * @param y  the y coordinate (in screen coordinates).
    2242         */
    2243        public void zoomInRange(double x, double y) {
    2244            Plot plot = this.chart.getPlot();
    2245            if (plot instanceof Zoomable) {
    2246                // here we tweak the notify flag on the plot so that only
    2247                // one notification happens even though we update multiple
    2248                // axes...
    2249                boolean savedNotify = plot.isNotify();
    2250                plot.setNotify(false);
    2251                Zoomable z = (Zoomable) plot;
    2252                z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(),
    2253                        translateScreenToJava2D(new Point((int) x, (int) y)),
    2254                        this.zoomAroundAnchor);
    2255                plot.setNotify(savedNotify);
    2256            }
    2257        }
    2258
    2259        /**
    2260         * Zooms out on an anchor point (specified in screen coordinate space).
    2261         *
    2262         * @param x  the x value (in screen coordinates).
    2263         * @param y  the y value (in screen coordinates).
    2264         */
    2265        public void zoomOutBoth(double x, double y) {
    2266            Plot plot = this.chart.getPlot();
    2267            if (plot == null) {
    2268                return;
    2269            }
    2270            // here we tweak the notify flag on the plot so that only
    2271            // one notification happens even though we update multiple
    2272            // axes...
    2273            boolean savedNotify = plot.isNotify();
    2274            plot.setNotify(false);
    2275            zoomOutDomain(x, y);
    2276            zoomOutRange(x, y);
    2277            plot.setNotify(savedNotify);
    2278        }
    2279
    2280        /**
    2281         * Increases the length of the domain axis, centered about the given
    2282         * coordinate on the screen.  The length of the domain axis is increased
    2283         * by the value of {@link #getZoomOutFactor()}.
    2284         *
    2285         * @param x  the x coordinate (in screen coordinates).
    2286         * @param y  the y-coordinate (in screen coordinates).
    2287         */
    2288        public void zoomOutDomain(double x, double y) {
    2289            Plot plot = this.chart.getPlot();
    2290            if (plot instanceof Zoomable) {
    2291                // here we tweak the notify flag on the plot so that only
    2292                // one notification happens even though we update multiple
    2293                // axes...
    2294                boolean savedNotify = plot.isNotify();
    2295                plot.setNotify(false);
    2296                Zoomable z = (Zoomable) plot;
    2297                z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(),
    2298                        translateScreenToJava2D(new Point((int) x, (int) y)),
    2299                        this.zoomAroundAnchor);
    2300                plot.setNotify(savedNotify);
    2301            }
    2302        }
    2303
    2304        /**
    2305         * Increases the length the range axis, centered about the given
    2306         * coordinate on the screen.  The length of the range axis is increased
    2307         * by the value of {@link #getZoomOutFactor()}.
    2308         *
    2309         * @param x  the x coordinate (in screen coordinates).
    2310         * @param y  the y-coordinate (in screen coordinates).
    2311         */
    2312        public void zoomOutRange(double x, double y) {
    2313            Plot plot = this.chart.getPlot();
    2314            if (plot instanceof Zoomable) {
    2315                // here we tweak the notify flag on the plot so that only
    2316                // one notification happens even though we update multiple
    2317                // axes...
    2318                boolean savedNotify = plot.isNotify();
    2319                plot.setNotify(false);
    2320                Zoomable z = (Zoomable) plot;
    2321                z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(),
    2322                        translateScreenToJava2D(new Point((int) x, (int) y)),
    2323                        this.zoomAroundAnchor);
    2324                plot.setNotify(savedNotify);
    2325            }
    2326        }
    2327
    2328        /**
    2329         * Zooms in on a selected region.
    2330         *
    2331         * @param selection  the selected region.
    2332         */
    2333        public void zoom(Rectangle2D selection) {
    2334
    2335            // get the origin of the zoom selection in the Java2D space used for
    2336            // drawing the chart (that is, before any scaling to fit the panel)
    2337            Point2D selectOrigin = translateScreenToJava2D(new Point(
    2338                    (int) Math.ceil(selection.getX()),
    2339                    (int) Math.ceil(selection.getY())));
    2340            PlotRenderingInfo plotInfo = this.info.getPlotInfo();
    2341            Rectangle2D scaledDataArea = getScreenDataArea(
    2342                    (int) selection.getCenterX(), (int) selection.getCenterY());
    2343            if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) {
    2344
    2345                double hLower = (selection.getMinX() - scaledDataArea.getMinX())
    2346                    / scaledDataArea.getWidth();
    2347                double hUpper = (selection.getMaxX() - scaledDataArea.getMinX())
    2348                    / scaledDataArea.getWidth();
    2349                double vLower = (scaledDataArea.getMaxY() - selection.getMaxY())
    2350                    / scaledDataArea.getHeight();
    2351                double vUpper = (scaledDataArea.getMaxY() - selection.getMinY())
    2352                    / scaledDataArea.getHeight();
    2353
    2354                Plot p = this.chart.getPlot();
    2355                if (p instanceof Zoomable) {
    2356                    // here we tweak the notify flag on the plot so that only
    2357                    // one notification happens even though we update multiple
    2358                    // axes...
    2359                    boolean savedNotify = p.isNotify();
    2360                    p.setNotify(false);
    2361                    Zoomable z = (Zoomable) p;
    2362                    if (z.getOrientation() == PlotOrientation.HORIZONTAL) {
    2363                        z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin);
    2364                        z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin);
    2365                    }
    2366                    else {
    2367                        z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin);
    2368                        z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin);
    2369                    }
    2370                    p.setNotify(savedNotify);
    2371                }
    2372
    2373            }
    2374
    2375        }
    2376
    2377        /**
    2378         * Restores the auto-range calculation on both axes.
    2379         */
    2380        public void restoreAutoBounds() {
    2381            Plot plot = this.chart.getPlot();
    2382            if (plot == null) {
    2383                return;
    2384            }
    2385            // here we tweak the notify flag on the plot so that only
    2386            // one notification happens even though we update multiple
    2387            // axes...
    2388            boolean savedNotify = plot.isNotify();
    2389            plot.setNotify(false);
    2390            restoreAutoDomainBounds();
    2391            restoreAutoRangeBounds();
    2392            plot.setNotify(savedNotify);
    2393        }
    2394
    2395        /**
    2396         * Restores the auto-range calculation on the domain axis.
    2397         */
    2398        public void restoreAutoDomainBounds() {
    2399            Plot plot = this.chart.getPlot();
    2400            if (plot instanceof Zoomable) {
    2401                Zoomable z = (Zoomable) plot;
    2402                // here we tweak the notify flag on the plot so that only
    2403                // one notification happens even though we update multiple
    2404                // axes...
    2405                boolean savedNotify = plot.isNotify();
    2406                plot.setNotify(false);
    2407                // we need to guard against this.zoomPoint being null
    2408                Point2D zp = (this.zoomPoint != null
    2409                        ? this.zoomPoint : new Point());
    2410                z.zoomDomainAxes(0.0, this.info.getPlotInfo(), zp);
    2411                plot.setNotify(savedNotify);
    2412            }
    2413        }
    2414
    2415        /**
    2416         * Restores the auto-range calculation on the range axis.
    2417         */
    2418        public void restoreAutoRangeBounds() {
    2419            Plot plot = this.chart.getPlot();
    2420            if (plot instanceof Zoomable) {
    2421                Zoomable z = (Zoomable) plot;
    2422                // here we tweak the notify flag on the plot so that only
    2423                // one notification happens even though we update multiple
    2424                // axes...
    2425                boolean savedNotify = plot.isNotify();
    2426                plot.setNotify(false);
    2427                // we need to guard against this.zoomPoint being null
    2428                Point2D zp = (this.zoomPoint != null
    2429                        ? this.zoomPoint : new Point());
    2430                z.zoomRangeAxes(0.0, this.info.getPlotInfo(), zp);
    2431                plot.setNotify(savedNotify);
    2432            }
    2433        }
    2434
    2435        /**
    2436         * Returns the data area for the chart (the area inside the axes) with the
    2437         * current scaling applied (that is, the area as it appears on screen).
    2438         *
    2439         * @return The scaled data area.
    2440         */
    2441        public Rectangle2D getScreenDataArea() {
    2442            Rectangle2D dataArea = this.info.getPlotInfo().getDataArea();
    2443            Insets insets = getInsets();
    2444            double x = dataArea.getX() * this.scaleX + insets.left;
    2445            double y = dataArea.getY() * this.scaleY + insets.top;
    2446            double w = dataArea.getWidth() * this.scaleX;
    2447            double h = dataArea.getHeight() * this.scaleY;
    2448            return new Rectangle2D.Double(x, y, w, h);
    2449        }
    2450
    2451        /**
    2452         * Returns the data area (the area inside the axes) for the plot or subplot,
    2453         * with the current scaling applied.
    2454         *
    2455         * @param x  the x-coordinate (for subplot selection).
    2456         * @param y  the y-coordinate (for subplot selection).
    2457         *
    2458         * @return The scaled data area.
    2459         */
    2460        public Rectangle2D getScreenDataArea(int x, int y) {
    2461            PlotRenderingInfo plotInfo = this.info.getPlotInfo();
    2462            Rectangle2D result;
    2463            if (plotInfo.getSubplotCount() == 0) {
    2464                result = getScreenDataArea();
    2465            }
    2466            else {
    2467                // get the origin of the zoom selection in the Java2D space used for
    2468                // drawing the chart (that is, before any scaling to fit the panel)
    2469                Point2D selectOrigin = translateScreenToJava2D(new Point(x, y));
    2470                int subplotIndex = plotInfo.getSubplotIndex(selectOrigin);
    2471                if (subplotIndex == -1) {
    2472                    return null;
    2473                }
    2474                result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea());
    2475            }
    2476            return result;
    2477        }
    2478
    2479        /**
    2480         * Returns the initial tooltip delay value used inside this chart panel.
    2481         *
    2482         * @return An integer representing the initial delay value, in milliseconds.
    2483         *
    2484         * @see javax.swing.ToolTipManager#getInitialDelay()
    2485         */
    2486        public int getInitialDelay() {
    2487            return this.ownToolTipInitialDelay;
    2488        }
    2489
    2490        /**
    2491         * Returns the reshow tooltip delay value used inside this chart panel.
    2492         *
    2493         * @return An integer representing the reshow  delay value, in milliseconds.
    2494         *
    2495         * @see javax.swing.ToolTipManager#getReshowDelay()
    2496         */
    2497        public int getReshowDelay() {
    2498            return this.ownToolTipReshowDelay;
    2499        }
    2500
    2501        /**
    2502         * Returns the dismissal tooltip delay value used inside this chart panel.
    2503         *
    2504         * @return An integer representing the dismissal delay value, in
    2505         *         milliseconds.
    2506         *
    2507         * @see javax.swing.ToolTipManager#getDismissDelay()
    2508         */
    2509        public int getDismissDelay() {
    2510            return this.ownToolTipDismissDelay;
    2511        }
    2512
    2513        /**
    2514         * Specifies the initial delay value for this chart panel.
    2515         *
    2516         * @param delay  the number of milliseconds to delay (after the cursor has
    2517         *               paused) before displaying.
    2518         *
    2519         * @see javax.swing.ToolTipManager#setInitialDelay(int)
    2520         */
    2521        public void setInitialDelay(int delay) {
    2522            this.ownToolTipInitialDelay = delay;
    2523        }
    2524
    2525        /**
    2526         * Specifies the amount of time before the user has to wait initialDelay
    2527         * milliseconds before a tooltip will be shown.
    2528         *
    2529         * @param delay  time in milliseconds
    2530         *
    2531         * @see javax.swing.ToolTipManager#setReshowDelay(int)
    2532         */
    2533        public void setReshowDelay(int delay) {
    2534            this.ownToolTipReshowDelay = delay;
    2535        }
    2536
    2537        /**
    2538         * Specifies the dismissal delay value for this chart panel.
    2539         *
    2540         * @param delay the number of milliseconds to delay before taking away the
    2541         *              tooltip
    2542         *
    2543         * @see javax.swing.ToolTipManager#setDismissDelay(int)
    2544         */
    2545        public void setDismissDelay(int delay) {
    2546            this.ownToolTipDismissDelay = delay;
    2547        }
    2548
    2549        /**
    2550         * Returns the zoom in factor.
    2551         *
    2552         * @return The zoom in factor.
    2553         *
    2554         * @see #setZoomInFactor(double)
    2555         */
    2556        public double getZoomInFactor() {
    2557            return this.zoomInFactor;
    2558        }
    2559
    2560        /**
    2561         * Sets the zoom in factor.
    2562         *
    2563         * @param factor  the factor.
    2564         *
    2565         * @see #getZoomInFactor()
    2566         */
    2567        public void setZoomInFactor(double factor) {
    2568            this.zoomInFactor = factor;
    2569        }
    2570
    2571        /**
    2572         * Returns the zoom out factor.
    2573         *
    2574         * @return The zoom out factor.
    2575         *
    2576         * @see #setZoomOutFactor(double)
    2577         */
    2578        public double getZoomOutFactor() {
    2579            return this.zoomOutFactor;
    2580        }
    2581
    2582        /**
    2583         * Sets the zoom out factor.
    2584         *
    2585         * @param factor  the factor.
    2586         *
    2587         * @see #getZoomOutFactor()
    2588         */
    2589        public void setZoomOutFactor(double factor) {
    2590            this.zoomOutFactor = factor;
    2591        }
    2592
    2593        /**
    2594         * Draws zoom rectangle (if present).
    2595         * The drawing is performed in XOR mode, therefore
    2596         * when this method is called twice in a row,
    2597         * the second call will completely restore the state
    2598         * of the canvas.
    2599         *
    2600         * @param g2 the graphics device.
    2601         * @param xor  use XOR for drawing?
    2602         */
    2603        private void drawZoomRectangle(Graphics2D g2, boolean xor) {
    2604            if (this.zoomRectangle != null) {
    2605                if (xor) {
    2606                     // Set XOR mode to draw the zoom rectangle
    2607                    g2.setXORMode(Color.gray);
    2608                }
    2609                if (this.fillZoomRectangle) {
    2610                    g2.setPaint(this.zoomFillPaint);
    2611                    g2.fill(this.zoomRectangle);
    2612                }
    2613                else {
    2614                    g2.setPaint(this.zoomOutlinePaint);
    2615                    g2.draw(this.zoomRectangle);
    2616                }
    2617                if (xor) {
    2618                    // Reset to the default 'overwrite' mode
    2619                    g2.setPaintMode();
    2620                }
    2621            }
    2622        }
    2623
    2624        /**
    2625         * Draws a vertical line used to trace the mouse position to the horizontal
    2626         * axis.
    2627         *
    2628         * @param g2 the graphics device.
    2629         * @param x  the x-coordinate of the trace line.
    2630         */
    2631        private void drawHorizontalAxisTrace(Graphics2D g2, int x) {
    2632
    2633            Rectangle2D dataArea = getScreenDataArea();
    2634
    2635            g2.setXORMode(Color.orange);
    2636            if (((int) dataArea.getMinX() < x) && (x < (int) dataArea.getMaxX())) {
    2637
    2638                if (this.verticalTraceLine != null) {
    2639                    g2.draw(this.verticalTraceLine);
    2640                    this.verticalTraceLine.setLine(x, (int) dataArea.getMinY(), x,
    2641                            (int) dataArea.getMaxY());
    2642                }
    2643                else {
    2644                    this.verticalTraceLine = new Line2D.Float(x,
    2645                            (int) dataArea.getMinY(), x, (int) dataArea.getMaxY());
    2646                }
    2647                g2.draw(this.verticalTraceLine);
    2648            }
    2649
    2650            // Reset to the default 'overwrite' mode
    2651            g2.setPaintMode();
    2652        }
    2653
    2654        /**
    2655         * Draws a horizontal line used to trace the mouse position to the vertical
    2656         * axis.
    2657         *
    2658         * @param g2 the graphics device.
    2659         * @param y  the y-coordinate of the trace line.
    2660         */
    2661        private void drawVerticalAxisTrace(Graphics2D g2, int y) {
    2662
    2663            Rectangle2D dataArea = getScreenDataArea();
    2664
    2665            g2.setXORMode(Color.orange);
    2666            if (((int) dataArea.getMinY() < y) && (y < (int) dataArea.getMaxY())) {
    2667
    2668                if (this.horizontalTraceLine != null) {
    2669                    g2.draw(this.horizontalTraceLine);
    2670                    this.horizontalTraceLine.setLine((int) dataArea.getMinX(), y,
    2671                            (int) dataArea.getMaxX(), y);
    2672                }
    2673                else {
    2674                    this.horizontalTraceLine = new Line2D.Float(
    2675                            (int) dataArea.getMinX(), y, (int) dataArea.getMaxX(),
    2676                            y);
    2677                }
    2678                g2.draw(this.horizontalTraceLine);
    2679            }
    2680
    2681            // Reset to the default 'overwrite' mode
    2682            g2.setPaintMode();
    2683        }
    2684
    2685        /**
    2686         * Displays a dialog that allows the user to edit the properties for the
    2687         * current chart.
    2688         *
    2689         * @since 1.0.3
    2690         */
    2691        public void doEditChartProperties() {
    2692
    2693            ChartEditor editor = ChartEditorManager.getChartEditor(this.chart);
    2694            int result = JOptionPane.showConfirmDialog(this, editor,
    2695                    localizationResources.getString("Chart_Properties"),
    2696                    JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
    2697            if (result == JOptionPane.OK_OPTION) {
    2698                editor.updateChart(this.chart);
    2699            }
    2700
    2701        }
    2702
    2703        /**
    2704         * Copies the current chart to the system clipboard.
    2705         *
    2706         * @since 1.0.13
    2707         */
    2708        public void doCopy() {
    2709            Clipboard systemClipboard
    2710                    = Toolkit.getDefaultToolkit().getSystemClipboard();
    2711            ChartTransferable selection = new ChartTransferable(this.chart,
    2712                    getWidth(), getHeight());
    2713            systemClipboard.setContents(selection, null);
    2714        }
    2715
    2716        /**
    2717         * Opens a file chooser and gives the user an opportunity to save the chart
    2718         * in PNG format.
    2719         *
    2720         * @throws IOException if there is an I/O error.
    2721         */
    2722        public void doSaveAs() throws IOException {
    2723
    2724            JFileChooser fileChooser = new JFileChooser();
    2725            fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
    2726            ExtensionFileFilter filter = new ExtensionFileFilter(
    2727                    localizationResources.getString("PNG_Image_Files"), ".png");
    2728            fileChooser.addChoosableFileFilter(filter);
    2729
    2730            int option = fileChooser.showSaveDialog(this);
    2731            if (option == JFileChooser.APPROVE_OPTION) {
    2732                String filename = fileChooser.getSelectedFile().getPath();
    2733                if (isEnforceFileExtensions()) {
    2734                    if (!filename.endsWith(".png")) {
    2735                        filename = filename + ".png";
    2736                    }
    2737                }
    2738                ChartUtilities.saveChartAsPNG(new File(filename), this.chart,
    2739                        getWidth(), getHeight());
    2740            }
    2741
    2742        }
    2743
    2744        /**
    2745         * Creates a print job for the chart.
    2746         */
    2747        public void createChartPrintJob() {
    2748
    2749            PrinterJob job = PrinterJob.getPrinterJob();
    2750            PageFormat pf = job.defaultPage();
    2751            PageFormat pf2 = job.pageDialog(pf);
    2752            if (pf2 != pf) {
    2753                job.setPrintable(this, pf2);
    2754                if (job.printDialog()) {
    2755                    try {
    2756                        job.print();
    2757                    }
    2758                    catch (PrinterException e) {
    2759                        JOptionPane.showMessageDialog(this, e);
    2760                    }
    2761                }
    2762            }
    2763
    2764        }
    2765
    2766        /**
    2767         * Prints the chart on a single page.
    2768         *
    2769         * @param g  the graphics context.
    2770         * @param pf  the page format to use.
    2771         * @param pageIndex  the index of the page. If not <code>0</code>, nothing
    2772         *                   gets print.
    2773         *
    2774         * @return The result of printing.
    2775         */
    2776        public int print(Graphics g, PageFormat pf, int pageIndex) {
    2777
    2778            if (pageIndex != 0) {
    2779                return NO_SUCH_PAGE;
    2780            }
    2781            Graphics2D g2 = (Graphics2D) g;
    2782            double x = pf.getImageableX();
    2783            double y = pf.getImageableY();
    2784            double w = pf.getImageableWidth();
    2785            double h = pf.getImageableHeight();
    2786            this.chart.draw(g2, new Rectangle2D.Double(x, y, w, h), this.anchor,
    2787                    null);
    2788            return PAGE_EXISTS;
    2789
    2790        }
    2791
    2792        /**
    2793         * Adds a listener to the list of objects listening for chart mouse events.
    2794         *
    2795         * @param listener  the listener (<code>null</code> not permitted).
    2796         */
    2797        public void addChartMouseListener(ChartMouseListener listener) {
    2798            if (listener == null) {
    2799                throw new IllegalArgumentException("Null 'listener' argument.");
    2800            }
    2801            this.chartMouseListeners.add(ChartMouseListener.class, listener);
    2802        }
    2803
    2804        /**
    2805         * Removes a listener from the list of objects listening for chart mouse
    2806         * events.
    2807         *
    2808         * @param listener  the listener.
    2809         */
    2810        public void removeChartMouseListener(ChartMouseListener listener) {
    2811            this.chartMouseListeners.remove(ChartMouseListener.class, listener);
    2812        }
    2813
    2814        /**
    2815         * Returns an array of the listeners of the given type registered with the
    2816         * panel.
    2817         *
    2818         * @param listenerType  the listener type.
    2819         *
    2820         * @return An array of listeners.
    2821         */
    2822        public EventListener[] getListeners(Class listenerType) {
    2823            if (listenerType == ChartMouseListener.class) {
    2824                // fetch listeners from local storage
    2825                return this.chartMouseListeners.getListeners(listenerType);
    2826            }
    2827            else {
    2828                return super.getListeners(listenerType);
    2829            }
    2830        }
    2831
    2832        /**
    2833         * Creates a popup menu for the panel.
    2834         *
    2835         * @param properties  include a menu item for the chart property editor.
    2836         * @param save  include a menu item for saving the chart.
    2837         * @param print  include a menu item for printing the chart.
    2838         * @param zoom  include menu items for zooming.
    2839         *
    2840         * @return The popup menu.
    2841         */
    2842        protected JPopupMenu createPopupMenu(boolean properties, boolean save,
    2843                boolean print, boolean zoom) {
    2844            return createPopupMenu(properties, false, save, print, zoom);
    2845        }
    2846
    2847        /**
    2848         * Creates a popup menu for the panel.
    2849         *
    2850         * @param properties  include a menu item for the chart property editor.
    2851         * @param copy include a menu item for copying to the clipboard.
    2852         * @param save  include a menu item for saving the chart.
    2853         * @param print  include a menu item for printing the chart.
    2854         * @param zoom  include menu items for zooming.
    2855         *
    2856         * @return The popup menu.
    2857         *
    2858         * @since 1.0.13
    2859         */
    2860        protected JPopupMenu createPopupMenu(boolean properties,
    2861                boolean copy, boolean save, boolean print, boolean zoom) {
    2862
    2863            JPopupMenu result = new JPopupMenu("Chart:");
    2864            boolean separator = false;
    2865
    2866            if (properties) {
    2867                JMenuItem propertiesItem = new JMenuItem(
    2868                        localizationResources.getString("Properties..."));
    2869                propertiesItem.setActionCommand(PROPERTIES_COMMAND);
    2870                propertiesItem.addActionListener(this);
    2871                result.add(propertiesItem);
    2872                separator = true;
    2873            }
    2874
    2875            if (copy) {
    2876                if (separator) {
    2877                    result.addSeparator();
    2878                    separator = false;
    2879                }
    2880                JMenuItem copyItem = new JMenuItem(
    2881                        localizationResources.getString("Copy"));
    2882                copyItem.setActionCommand(COPY_COMMAND);
    2883                copyItem.addActionListener(this);
    2884                result.add(copyItem);
    2885                separator = !save;
    2886            }
    2887
    2888            if (save) {
    2889                if (separator) {
    2890                    result.addSeparator();
    2891                    separator = false;
    2892                }
    2893                JMenuItem saveItem = new JMenuItem(
    2894                        localizationResources.getString("Save_as..."));
    2895                saveItem.setActionCommand(SAVE_COMMAND);
    2896                saveItem.addActionListener(this);
    2897                result.add(saveItem);
    2898                separator = true;
    2899            }
    2900
    2901            if (print) {
    2902                if (separator) {
    2903                    result.addSeparator();
    2904                    separator = false;
    2905                }
    2906                JMenuItem printItem = new JMenuItem(
    2907                        localizationResources.getString("Print..."));
    2908                printItem.setActionCommand(PRINT_COMMAND);
    2909                printItem.addActionListener(this);
    2910                result.add(printItem);
    2911                separator = true;
    2912            }
    2913
    2914            if (zoom) {
    2915                if (separator) {
    2916                    result.addSeparator();
    2917                    separator = false;
    2918                }
    2919
    2920                JMenu zoomInMenu = new JMenu(
    2921                        localizationResources.getString("Zoom_In"));
    2922
    2923                this.zoomInBothMenuItem = new JMenuItem(
    2924                        localizationResources.getString("All_Axes"));
    2925                this.zoomInBothMenuItem.setActionCommand(ZOOM_IN_BOTH_COMMAND);
    2926                this.zoomInBothMenuItem.addActionListener(this);
    2927                zoomInMenu.add(this.zoomInBothMenuItem);
    2928
    2929                zoomInMenu.addSeparator();
    2930
    2931                this.zoomInDomainMenuItem = new JMenuItem(
    2932                        localizationResources.getString("Domain_Axis"));
    2933                this.zoomInDomainMenuItem.setActionCommand(ZOOM_IN_DOMAIN_COMMAND);
    2934                this.zoomInDomainMenuItem.addActionListener(this);
    2935                zoomInMenu.add(this.zoomInDomainMenuItem);
    2936
    2937                this.zoomInRangeMenuItem = new JMenuItem(
    2938                        localizationResources.getString("Range_Axis"));
    2939                this.zoomInRangeMenuItem.setActionCommand(ZOOM_IN_RANGE_COMMAND);
    2940                this.zoomInRangeMenuItem.addActionListener(this);
    2941                zoomInMenu.add(this.zoomInRangeMenuItem);
    2942
    2943                result.add(zoomInMenu);
    2944
    2945                JMenu zoomOutMenu = new JMenu(
    2946                        localizationResources.getString("Zoom_Out"));
    2947
    2948                this.zoomOutBothMenuItem = new JMenuItem(
    2949                        localizationResources.getString("All_Axes"));
    2950                this.zoomOutBothMenuItem.setActionCommand(ZOOM_OUT_BOTH_COMMAND);
    2951                this.zoomOutBothMenuItem.addActionListener(this);
    2952                zoomOutMenu.add(this.zoomOutBothMenuItem);
    2953
    2954                zoomOutMenu.addSeparator();
    2955
    2956                this.zoomOutDomainMenuItem = new JMenuItem(
    2957                        localizationResources.getString("Domain_Axis"));
    2958                this.zoomOutDomainMenuItem.setActionCommand(
    2959                        ZOOM_OUT_DOMAIN_COMMAND);
    2960                this.zoomOutDomainMenuItem.addActionListener(this);
    2961                zoomOutMenu.add(this.zoomOutDomainMenuItem);
    2962
    2963                this.zoomOutRangeMenuItem = new JMenuItem(
    2964                        localizationResources.getString("Range_Axis"));
    2965                this.zoomOutRangeMenuItem.setActionCommand(ZOOM_OUT_RANGE_COMMAND);
    2966                this.zoomOutRangeMenuItem.addActionListener(this);
    2967                zoomOutMenu.add(this.zoomOutRangeMenuItem);
    2968
    2969                result.add(zoomOutMenu);
    2970
    2971                JMenu autoRangeMenu = new JMenu(
    2972                        localizationResources.getString("Auto_Range"));
    2973
    2974                this.zoomResetBothMenuItem = new JMenuItem(
    2975                        localizationResources.getString("All_Axes"));
    2976                this.zoomResetBothMenuItem.setActionCommand(
    2977                        ZOOM_RESET_BOTH_COMMAND);
    2978                this.zoomResetBothMenuItem.addActionListener(this);
    2979                autoRangeMenu.add(this.zoomResetBothMenuItem);
    2980
    2981                autoRangeMenu.addSeparator();
    2982                this.zoomResetDomainMenuItem = new JMenuItem(
    2983                        localizationResources.getString("Domain_Axis"));
    2984                this.zoomResetDomainMenuItem.setActionCommand(
    2985                        ZOOM_RESET_DOMAIN_COMMAND);
    2986                this.zoomResetDomainMenuItem.addActionListener(this);
    2987                autoRangeMenu.add(this.zoomResetDomainMenuItem);
    2988
    2989                this.zoomResetRangeMenuItem = new JMenuItem(
    2990                        localizationResources.getString("Range_Axis"));
    2991                this.zoomResetRangeMenuItem.setActionCommand(
    2992                        ZOOM_RESET_RANGE_COMMAND);
    2993                this.zoomResetRangeMenuItem.addActionListener(this);
    2994                autoRangeMenu.add(this.zoomResetRangeMenuItem);
    2995
    2996                result.addSeparator();
    2997                result.add(autoRangeMenu);
    2998
    2999            }
    3000
    3001            return result;
    3002
    3003        }
    3004
    3005        /**
    3006         * The idea is to modify the zooming options depending on the type of chart
    3007         * being displayed by the panel.
    3008         *
    3009         * @param x  horizontal position of the popup.
    3010         * @param y  vertical position of the popup.
    3011         */
    3012        protected void displayPopupMenu(int x, int y) {
    3013
    3014            if (this.popup != null) {
    3015
    3016                // go through each zoom menu item and decide whether or not to
    3017                // enable it...
    3018                Plot plot = this.chart.getPlot();
    3019                boolean isDomainZoomable = false;
    3020                boolean isRangeZoomable = false;
    3021                if (plot instanceof Zoomable) {
    3022                    Zoomable z = (Zoomable) plot;
    3023                    isDomainZoomable = z.isDomainZoomable();
    3024                    isRangeZoomable = z.isRangeZoomable();
    3025                }
    3026
    3027                if (this.zoomInDomainMenuItem != null) {
    3028                    this.zoomInDomainMenuItem.setEnabled(isDomainZoomable);
    3029                }
    3030                if (this.zoomOutDomainMenuItem != null) {
    3031                    this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable);
    3032                }
    3033                if (this.zoomResetDomainMenuItem != null) {
    3034                    this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable);
    3035                }
    3036
    3037                if (this.zoomInRangeMenuItem != null) {
    3038                    this.zoomInRangeMenuItem.setEnabled(isRangeZoomable);
    3039                }
    3040                if (this.zoomOutRangeMenuItem != null) {
    3041                    this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable);
    3042                }
    3043
    3044                if (this.zoomResetRangeMenuItem != null) {
    3045                    this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable);
    3046                }
    3047
    3048                if (this.zoomInBothMenuItem != null) {
    3049                    this.zoomInBothMenuItem.setEnabled(isDomainZoomable
    3050                            && isRangeZoomable);
    3051                }
    3052                if (this.zoomOutBothMenuItem != null) {
    3053                    this.zoomOutBothMenuItem.setEnabled(isDomainZoomable
    3054                            && isRangeZoomable);
    3055                }
    3056                if (this.zoomResetBothMenuItem != null) {
    3057                    this.zoomResetBothMenuItem.setEnabled(isDomainZoomable
    3058                            && isRangeZoomable);
    3059                }
    3060
    3061                this.popup.show(this, x, y);
    3062            }
    3063
    3064        }
    3065
    3066        /**
    3067         * Updates the UI for a LookAndFeel change.
    3068         */
    3069        public void updateUI() {
    3070            // here we need to update the UI for the popup menu, if the panel
    3071            // has one...
    3072            if (this.popup != null) {
    3073                SwingUtilities.updateComponentTreeUI(this.popup);
    3074            }
    3075            super.updateUI();
    3076        }
    3077
    3078        /**
    3079         * Provides serialization support.
    3080         *
    3081         * @param stream  the output stream.
    3082         *
    3083         * @throws IOException  if there is an I/O error.
    3084         */
    3085        private void writeObject(ObjectOutputStream stream) throws IOException {
    3086            stream.defaultWriteObject();
    3087            SerialUtilities.writePaint(this.zoomFillPaint, stream);
    3088            SerialUtilities.writePaint(this.zoomOutlinePaint, stream);
    3089        }
    3090
    3091        /**
    3092         * Provides serialization support.
    3093         *
    3094         * @param stream  the input stream.
    3095         *
    3096         * @throws IOException  if there is an I/O error.
    3097         * @throws ClassNotFoundException  if there is a classpath problem.
    3098         */
    3099        private void readObject(ObjectInputStream stream)
    3100            throws IOException, ClassNotFoundException {
    3101            stream.defaultReadObject();
    3102            this.zoomFillPaint = SerialUtilities.readPaint(stream);
    3103            this.zoomOutlinePaint = SerialUtilities.readPaint(stream);
    3104
    3105            // we create a new but empty chartMouseListeners list
    3106            this.chartMouseListeners = new EventListenerList();
    3107
    3108            // register as a listener with sub-components...
    3109            if (this.chart != null) {
    3110                this.chart.addChangeListener(this);
    3111            }
    3112
    3113        }
    3114
    3115    }