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