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