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