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