    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
    170    package org.jfree.chart;
    171
    172    import java.awt.AWTEvent;
    173    import java.awt.AlphaComposite;
    174    import java.awt.Color;
    175    import java.awt.Composite;
    176    import java.awt.Cursor;
    177    import java.awt.Dimension;
    178    import java.awt.Graphics;
    179    import java.awt.Graphics2D;
    180    import java.awt.GraphicsConfiguration;
    181    import java.awt.Image;
    182    import java.awt.Insets;
    183    import java.awt.Paint;
    184    import java.awt.Point;
    185    import java.awt.Rectangle;
    186    import java.awt.Toolkit;
    187    import java.awt.Transparency;
    188    import java.awt.datatransfer.Clipboard;
    189    import java.awt.event.ActionEvent;
    190    import java.awt.event.ActionListener;
    191    import java.awt.event.InputEvent;
    192    import java.awt.event.MouseEvent;
    193    import java.awt.event.MouseListener;
    194    import java.awt.event.MouseMotionListener;
    195    import java.awt.geom.AffineTransform;
    196    import java.awt.geom.Line2D;
    197    import java.awt.geom.Point2D;
    198    import java.awt.geom.Rectangle2D;
    199    import java.awt.print.PageFormat;
    200    import java.awt.print.Printable;
    201    import java.awt.print.PrinterException;
    202    import java.awt.print.PrinterJob;
    203    import java.io.File;
    204    import java.io.IOException;
    205    import java.io.ObjectInputStream;
    206    import java.io.ObjectOutputStream;
    207    import java.io.Serializable;
    208    import java.lang.reflect.Constructor;
    209    import java.lang.reflect.InvocationTargetException;
    210    import java.lang.reflect.Method;
    211    import java.util.EventListener;
    212    import java.util.Iterator;
    213    import java.util.List;
    214    import java.util.ResourceBundle;
    215
    216    import javax.swing.JFileChooser;
    217    import javax.swing.JMenu;
    218    import javax.swing.JMenuItem;
    219    import javax.swing.JOptionPane;
    220    import javax.swing.JPanel;
    221    import javax.swing.JPopupMenu;
    222    import javax.swing.SwingUtilities;
    223    import javax.swing.ToolTipManager;
    224    import javax.swing.event.EventListenerList;
    225
    226    import org.jfree.chart.editor.ChartEditor;
    227    import org.jfree.chart.editor.ChartEditorManager;
    228    import org.jfree.chart.entity.ChartEntity;
    229    import org.jfree.chart.entity.EntityCollection;
    230    import org.jfree.chart.event.ChartChangeEvent;
    231    import org.jfree.chart.event.ChartChangeListener;
    232    import org.jfree.chart.event.ChartProgressEvent;
    233    import org.jfree.chart.event.ChartProgressListener;
    234    import org.jfree.chart.panel.Overlay;
    235    import org.jfree.chart.event.OverlayChangeEvent;
    236    import org.jfree.chart.event.OverlayChangeListener;
    237    import org.jfree.chart.plot.Pannable;
    238    import org.jfree.chart.plot.Plot;
    239    import org.jfree.chart.plot.PlotOrientation;
    240    import org.jfree.chart.plot.PlotRenderingInfo;
    241    import org.jfree.chart.plot.Zoomable;
    242    import org.jfree.chart.util.ResourceBundleWrapper;
    243    import org.jfree.io.SerialUtilities;
    244    import 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     */
    253    public 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    }