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