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