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
142package org.jfree.chart;
143
144import java.awt.AWTEvent;
145import java.awt.Color;
146import java.awt.Dimension;
147import java.awt.Graphics;
148import java.awt.Graphics2D;
149import java.awt.Image;
150import java.awt.Insets;
151import java.awt.Point;
152import java.awt.event.ActionEvent;
153import java.awt.event.ActionListener;
154import java.awt.event.MouseEvent;
155import java.awt.event.MouseListener;
156import java.awt.event.MouseMotionListener;
157import java.awt.geom.AffineTransform;
158import java.awt.geom.Line2D;
159import java.awt.geom.Point2D;
160import java.awt.geom.Rectangle2D;
161import java.awt.print.PageFormat;
162import java.awt.print.Printable;
163import java.awt.print.PrinterException;
164import java.awt.print.PrinterJob;
165import java.io.File;
166import java.io.IOException;
167import java.io.Serializable;
168import java.util.EventListener;
169import java.util.ResourceBundle;
170
171import javax.swing.JFileChooser;
172import javax.swing.JMenu;
173import javax.swing.JMenuItem;
174import javax.swing.JOptionPane;
175import javax.swing.JPanel;
176import javax.swing.JPopupMenu;
177import javax.swing.SwingUtilities;
178import javax.swing.ToolTipManager;
179import javax.swing.event.EventListenerList;
180
181import org.jfree.chart.editor.ChartEditor;
182import org.jfree.chart.editor.ChartEditorManager;
183import org.jfree.chart.entity.ChartEntity;
184import org.jfree.chart.entity.EntityCollection;
185import org.jfree.chart.event.ChartChangeEvent;
186import org.jfree.chart.event.ChartChangeListener;
187import org.jfree.chart.event.ChartProgressEvent;
188import org.jfree.chart.event.ChartProgressListener;
189import org.jfree.chart.plot.Plot;
190import org.jfree.chart.plot.PlotOrientation;
191import org.jfree.chart.plot.PlotRenderingInfo;
192import org.jfree.chart.plot.Zoomable;
193import 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 */
202public 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}