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