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