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