001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------
028 * ChartPanel.java
029 * ---------------
030 * (C) Copyright 2000-2013, 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 *                   Ulrich Voigt - patch 2686040;
045 *                   Alessandro Borges - patch 1460845;
046 *                   Martin Hoeller;
047 *
048 * Changes (from 28-Jun-2001)
049 * --------------------------
050 * 28-Jun-2001 : Integrated buffering code contributed by S???ren
051 *               Caspersen (DG);
052 * 18-Sep-2001 : Updated header and fixed DOS encoding problem (DG);
053 * 22-Nov-2001 : Added scaling to improve display of charts in small sizes (DG);
054 * 26-Nov-2001 : Added property editing, saving and printing (DG);
055 * 11-Dec-2001 : Transferred saveChartAsPNG method to new ChartUtilities
056 *               class (DG);
057 * 13-Dec-2001 : Added tooltips (DG);
058 * 16-Jan-2002 : Added an optional crosshair, based on the implementation by
059 *               Jonathan Nash. Renamed the tooltips class (DG);
060 * 23-Jan-2002 : Implemented zooming based on code by Hans-Jurgen Greiner (DG);
061 * 05-Feb-2002 : Improved tooltips setup.  Renamed method attemptSaveAs()
062 *               --> doSaveAs() and made it public rather than private (DG);
063 * 28-Mar-2002 : Added a new constructor (DG);
064 * 09-Apr-2002 : Changed initialisation of tooltip generation, as suggested by
065 *               Hans-Jurgen Greiner (DG);
066 * 27-May-2002 : New interactive zooming methods based on code by Hans-Jurgen
067 *               Greiner. Renamed JFreeChartPanel --> ChartPanel, moved
068 *               constants to ChartPanelConstants interface (DG);
069 * 31-May-2002 : Fixed a bug with interactive zooming and added a way to
070 *               control if the zoom rectangle is filled in or drawn as an
071 *               outline. A mouse drag gesture towards the top left now causes
072 *               an autoRangeBoth() and is a way to undo zooms (AS);
073 * 11-Jun-2002 : Reinstated handleClick method call in mouseClicked() to get
074 *               crosshairs working again (DG);
075 * 13-Jun-2002 : Added check for null popup menu in mouseDragged method (DG);
076 * 18-Jun-2002 : Added get/set methods for minimum and maximum chart
077 *               dimensions (DG);
078 * 25-Jun-2002 : Removed redundant code (DG);
079 * 27-Aug-2002 : Added get/set methods for popup menu (DG);
080 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
081 * 22-Oct-2002 : Added translation methods for screen <--> Java2D, contributed
082 *               by Daniel van Enckevort (DG);
083 * 05-Nov-2002 : Added a chart reference to the ChartMouseEvent class (DG);
084 * 22-Nov-2002 : Added test in zoom method for inverted axes, supplied by
085 *               David M O'Donnell (DG);
086 * 14-Jan-2003 : Implemented ChartProgressListener interface (DG);
087 * 14-Feb-2003 : Removed deprecated setGenerateTooltips method (DG);
088 * 12-Mar-2003 : Added option to enforce filename extension (see bug id
089 *               643173) (DG);
090 * 08-Sep-2003 : Added internationalization via use of properties
091 *               resourceBundle (RFE 690236) (AL);
092 * 18-Sep-2003 : Added getScaleX() and getScaleY() methods (protected) as
093 *               requested by Irv Thomae (DG);
094 * 12-Nov-2003 : Added zooming support for the FastScatterPlot class (DG);
095 * 24-Nov-2003 : Minor Javadoc updates (DG);
096 * 04-Dec-2003 : Added anchor point for crosshair calculation (DG);
097 * 17-Jan-2004 : Added new methods to set tooltip delays to be used in this
098 *               chart panel. Refer to patch 877565 (MR);
099 * 02-Feb-2004 : Fixed bug in zooming trigger and added zoomTriggerDistance
100 *               attribute (DG);
101 * 08-Apr-2004 : Changed getScaleX() and getScaleY() from protected to
102 *               public (DG);
103 * 15-Apr-2004 : Added zoomOutFactor and zoomInFactor (DG);
104 * 21-Apr-2004 : Fixed zooming bug in mouseReleased() method (DG);
105 * 13-Jul-2004 : Added check for null chart (DG);
106 * 04-Oct-2004 : Renamed ShapeUtils --> ShapeUtilities (DG);
107 * 11-Nov-2004 : Moved constants back in from ChartPanelConstants (DG);
108 * 12-Nov-2004 : Modified zooming mechanism to support zooming within
109 *               subplots (DG);
110 * 26-Jan-2005 : Fixed mouse zooming for horizontal category plots (DG);
111 * 11-Apr-2005 : Added getFillZoomRectangle() method, renamed
112 *               setHorizontalZoom() --> setDomainZoomable(),
113 *               setVerticalZoom() --> setRangeZoomable(), added
114 *               isDomainZoomable() and isRangeZoomable(), added
115 *               getHorizontalAxisTrace() and getVerticalAxisTrace(),
116 *               renamed autoRangeBoth() --> restoreAutoBounds(),
117 *               autoRangeHorizontal() --> restoreAutoDomainBounds(),
118 *               autoRangeVertical() --> restoreAutoRangeBounds() (DG);
119 * 12-Apr-2005 : Removed working areas, added getAnchorPoint() method,
120 *               added protected accessors for tracelines (DG);
121 * 18-Apr-2005 : Made constants final (DG);
122 * 26-Apr-2005 : Removed LOGGER (DG);
123 * 01-Jun-2005 : Fixed zooming for combined plots - see bug report
124 *               1212039, fix thanks to Onno vd Akker (DG);
125 * 25-Nov-2005 : Reworked event listener mechanism (DG);
126 * ------------- JFREECHART 1.0.x ---------------------------------------------
127 * 01-Aug-2006 : Fixed minor bug in restoreAutoRangeBounds() (DG);
128 * 04-Sep-2006 : Renamed attemptEditChartProperties() -->
129 *               doEditChartProperties() and made public (DG);
130 * 13-Sep-2006 : Don't generate ChartMouseEvents if the panel's chart is null
131 *               (fixes bug 1556951) (DG);
132 * 05-Mar-2007 : Applied patch 1672561 by Sergei Ivanov, to fix zoom rectangle
133 *               drawing for dynamic charts (DG);
134 * 17-Apr-2007 : Fix NullPointerExceptions in zooming for combined plots (DG);
135 * 24-May-2007 : When the look-and-feel changes, update the popup menu if there
136 *               is one (DG);
137 * 06-Jun-2007 : Fixed coordinates for drawing buffer image (DG);
138 * 24-Sep-2007 : Added zoomAroundAnchor flag, and handle clearing of chart
139 *               buffer (DG);
140 * 25-Oct-2007 : Added default directory attribute (DG);
141 * 07-Nov-2007 : Fixed (rare) bug in refreshing off-screen image (DG);
142 * 07-May-2008 : Fixed bug in zooming that triggered zoom for a rectangle
143 *               outside of the data area (DG);
144 * 08-May-2008 : Fixed serialization bug (DG);
145 * 15-Aug-2008 : Increased default maxDrawWidth/Height (DG);
146 * 18-Sep-2008 : Modified creation of chart buffer (DG);
147 * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
148 *               Jess Thrysoee (DG);
149 * 13-Jan-2009 : Fixed zooming methods to trigger only one plot
150 *               change event (DG);
151 * 16-Jan-2009 : Use XOR for zoom rectangle only if useBuffer is false (DG);
152 * 18-Mar-2009 : Added mouse wheel support (DG);
153 * 19-Mar-2009 : Added panning on mouse drag support - based on Ulrich 
154 *               Voigt's patch 2686040 (DG);
155 * 26-Mar-2009 : Changed fillZoomRectangle default to true, and only change
156 *               cursor for CTRL-mouse-click if panning is enabled (DG);
157 * 01-Apr-2009 : Fixed panning, and added different mouse event mask for
158 *               MacOSX (DG);
159 * 08-Apr-2009 : Added copy to clipboard support, based on patch 1460845
160 *               by Alessandro Borges (DG);
161 * 09-Apr-2009 : Added overlay support (DG);
162 * 10-Apr-2009 : Set chartBuffer background to match ChartPanel (DG);
163 * 05-May-2009 : Match scaling (and insets) in doCopy() (DG);
164 * 01-Jun-2009 : Check for null chart in mousePressed() method (DG);
165 * 08-Jun-2009 : Fixed bug in setMouseWheelEnabled() (DG);
166 * 06-Jul-2009 : Clear off-screen buffer to fully transparent (DG);
167 * 10-Oct-2011 : localization fix: bug #3353913 (MH);
168 * 05-Jul-2012 : Remove reflection for MouseWheelListener - only needed for 
169 *               JRE 1.3.1 (DG);
170 * 02-Jul-2013 : Use ParamChecks class (DG);
171 * 12-Sep-2013 : Provide auto-detection for JFreeSVG and OrsonPDF 
172 *               libraries (no compile time dependencies) (DG);
173 * 
174 */
175
176package org.jfree.chart;
177
178import java.awt.AWTEvent;
179import java.awt.AlphaComposite;
180import java.awt.Color;
181import java.awt.Composite;
182import java.awt.Cursor;
183import java.awt.Dimension;
184import java.awt.Graphics;
185import java.awt.Graphics2D;
186import java.awt.GraphicsConfiguration;
187import java.awt.Image;
188import java.awt.Insets;
189import java.awt.Paint;
190import java.awt.Point;
191import java.awt.Rectangle;
192import java.awt.Toolkit;
193import java.awt.Transparency;
194import java.awt.datatransfer.Clipboard;
195import java.awt.event.ActionEvent;
196import java.awt.event.ActionListener;
197import java.awt.event.InputEvent;
198import java.awt.event.MouseEvent;
199import java.awt.event.MouseListener;
200import java.awt.event.MouseMotionListener;
201import java.awt.geom.AffineTransform;
202import java.awt.geom.Line2D;
203import java.awt.geom.Point2D;
204import java.awt.geom.Rectangle2D;
205import java.awt.print.PageFormat;
206import java.awt.print.Printable;
207import java.awt.print.PrinterException;
208import java.awt.print.PrinterJob;
209import java.io.BufferedWriter;
210import java.io.File;
211import java.io.FileWriter;
212import java.io.IOException;
213import java.io.ObjectInputStream;
214import java.io.ObjectOutputStream;
215import java.io.Serializable;
216import java.lang.reflect.Constructor;
217import java.lang.reflect.InvocationTargetException;
218import java.lang.reflect.Method;
219import java.util.EventListener;
220import java.util.Iterator;
221import java.util.List;
222import java.util.ResourceBundle;
223
224import javax.swing.JFileChooser;
225import javax.swing.JMenu;
226import javax.swing.JMenuItem;
227import javax.swing.JOptionPane;
228import javax.swing.JPanel;
229import javax.swing.JPopupMenu;
230import javax.swing.SwingUtilities;
231import javax.swing.ToolTipManager;
232import javax.swing.event.EventListenerList;
233import javax.swing.filechooser.FileNameExtensionFilter;
234
235import org.jfree.chart.editor.ChartEditor;
236import org.jfree.chart.editor.ChartEditorManager;
237import org.jfree.chart.entity.ChartEntity;
238import org.jfree.chart.entity.EntityCollection;
239import org.jfree.chart.event.ChartChangeEvent;
240import org.jfree.chart.event.ChartChangeListener;
241import org.jfree.chart.event.ChartProgressEvent;
242import org.jfree.chart.event.ChartProgressListener;
243import org.jfree.chart.panel.Overlay;
244import org.jfree.chart.event.OverlayChangeEvent;
245import org.jfree.chart.event.OverlayChangeListener;
246import org.jfree.chart.plot.Pannable;
247import org.jfree.chart.plot.Plot;
248import org.jfree.chart.plot.PlotOrientation;
249import org.jfree.chart.plot.PlotRenderingInfo;
250import org.jfree.chart.plot.Zoomable;
251import org.jfree.chart.util.ParamChecks;
252import org.jfree.chart.util.ResourceBundleWrapper;
253import org.jfree.io.SerialUtilities;
254
255/**
256 * A Swing GUI component for displaying a {@link JFreeChart} object.
257 * <P>
258 * The panel registers with the chart to receive notification of changes to any
259 * component of the chart.  The chart is redrawn automatically whenever this
260 * notification is received.
261 */
262public class ChartPanel extends JPanel implements ChartChangeListener,
263        ChartProgressListener, ActionListener, MouseListener,
264        MouseMotionListener, OverlayChangeListener, Printable, Serializable {
265
266    /** For serialization. */
267    private static final long serialVersionUID = 6046366297214274674L;
268
269    /**
270     * Default setting for buffer usage.  The default has been changed to
271     * <code>true</code> from version 1.0.13 onwards, because of a severe
272     * performance problem with drawing the zoom rectangle using XOR (which
273     * now happens only when the buffer is NOT used).
274     */
275    public static final boolean DEFAULT_BUFFER_USED = true;
276
277    /** The default panel width. */
278    public static final int DEFAULT_WIDTH = 680;
279
280    /** The default panel height. */
281    public static final int DEFAULT_HEIGHT = 420;
282
283    /** The default limit below which chart scaling kicks in. */
284    public static final int DEFAULT_MINIMUM_DRAW_WIDTH = 300;
285
286    /** The default limit below which chart scaling kicks in. */
287    public static final int DEFAULT_MINIMUM_DRAW_HEIGHT = 200;
288
289    /** The default limit above which chart scaling kicks in. */
290    public static final int DEFAULT_MAXIMUM_DRAW_WIDTH = 1024;
291
292    /** The default limit above which chart scaling kicks in. */
293    public static final int DEFAULT_MAXIMUM_DRAW_HEIGHT = 768;
294
295    /** The minimum size required to perform a zoom on a rectangle */
296    public static final int DEFAULT_ZOOM_TRIGGER_DISTANCE = 10;
297
298    /** Properties action command. */
299    public static final String PROPERTIES_COMMAND = "PROPERTIES";
300
301    /**
302     * Copy action command.
303     *
304     * @since 1.0.13
305     */
306    public static final String COPY_COMMAND = "COPY";
307
308    /** Save action command. */
309    public static final String SAVE_COMMAND = "SAVE";
310
311    /** Action command to save as PNG. */
312    private static final String SAVE_AS_PNG_COMMAND = "SAVE_AS_PNG";
313    
314    /** Action command to save as SVG. */
315    private static final String SAVE_AS_SVG_COMMAND = "SAVE_AS_SVG";
316    
317    /** Action command to save as PDF. */
318    private static final String SAVE_AS_PDF_COMMAND = "SAVE_AS_PDF";
319    
320    /** Print action command. */
321    public static final String PRINT_COMMAND = "PRINT";
322
323    /** Zoom in (both axes) action command. */
324    public static final String ZOOM_IN_BOTH_COMMAND = "ZOOM_IN_BOTH";
325
326    /** Zoom in (domain axis only) action command. */
327    public static final String ZOOM_IN_DOMAIN_COMMAND = "ZOOM_IN_DOMAIN";
328
329    /** Zoom in (range axis only) action command. */
330    public static final String ZOOM_IN_RANGE_COMMAND = "ZOOM_IN_RANGE";
331
332    /** Zoom out (both axes) action command. */
333    public static final String ZOOM_OUT_BOTH_COMMAND = "ZOOM_OUT_BOTH";
334
335    /** Zoom out (domain axis only) action command. */
336    public static final String ZOOM_OUT_DOMAIN_COMMAND = "ZOOM_DOMAIN_BOTH";
337
338    /** Zoom out (range axis only) action command. */
339    public static final String ZOOM_OUT_RANGE_COMMAND = "ZOOM_RANGE_BOTH";
340
341    /** Zoom reset (both axes) action command. */
342    public static final String ZOOM_RESET_BOTH_COMMAND = "ZOOM_RESET_BOTH";
343
344    /** Zoom reset (domain axis only) action command. */
345    public static final String ZOOM_RESET_DOMAIN_COMMAND = "ZOOM_RESET_DOMAIN";
346
347    /** Zoom reset (range axis only) action command. */
348    public static final String ZOOM_RESET_RANGE_COMMAND = "ZOOM_RESET_RANGE";
349
350    /** The chart that is displayed in the panel. */
351    private JFreeChart chart;
352
353    /** Storage for registered (chart) mouse listeners. */
354    private transient EventListenerList chartMouseListeners;
355
356    /** A flag that controls whether or not the off-screen buffer is used. */
357    private boolean useBuffer;
358
359    /** A flag that indicates that the buffer should be refreshed. */
360    private boolean refreshBuffer;
361
362    /** A buffer for the rendered chart. */
363    private transient Image chartBuffer;
364
365    /** The height of the chart buffer. */
366    private int chartBufferHeight;
367
368    /** The width of the chart buffer. */
369    private int chartBufferWidth;
370
371    /**
372     * The minimum width for drawing a chart (uses scaling for smaller widths).
373     */
374    private int minimumDrawWidth;
375
376    /**
377     * The minimum height for drawing a chart (uses scaling for smaller
378     * heights).
379     */
380    private int minimumDrawHeight;
381
382    /**
383     * The maximum width for drawing a chart (uses scaling for bigger
384     * widths).
385     */
386    private int maximumDrawWidth;
387
388    /**
389     * The maximum height for drawing a chart (uses scaling for bigger
390     * heights).
391     */
392    private int maximumDrawHeight;
393
394    /** The popup menu for the frame. */
395    private JPopupMenu popup;
396
397    /** The drawing info collected the last time the chart was drawn. */
398    private ChartRenderingInfo info;
399
400    /** The chart anchor point. */
401    private Point2D anchor;
402
403    /** The scale factor used to draw the chart. */
404    private double scaleX;
405
406    /** The scale factor used to draw the chart. */
407    private double scaleY;
408
409    /** The plot orientation. */
410    private PlotOrientation orientation = PlotOrientation.VERTICAL;
411
412    /** A flag that controls whether or not domain zooming is enabled. */
413    private boolean domainZoomable = false;
414
415    /** A flag that controls whether or not range zooming is enabled. */
416    private boolean rangeZoomable = false;
417
418    /**
419     * The zoom rectangle starting point (selected by the user with a mouse
420     * click).  This is a point on the screen, not the chart (which may have
421     * been scaled up or down to fit the panel).
422     */
423    private Point2D zoomPoint = null;
424
425    /** The zoom rectangle (selected by the user with the mouse). */
426    private transient Rectangle2D zoomRectangle = null;
427
428    /** Controls if the zoom rectangle is drawn as an outline or filled. */
429    private boolean fillZoomRectangle = true;
430
431    /** The minimum distance required to drag the mouse to trigger a zoom. */
432    private int zoomTriggerDistance;
433
434    /** A flag that controls whether or not horizontal tracing is enabled. */
435    private boolean horizontalAxisTrace = false;
436
437    /** A flag that controls whether or not vertical tracing is enabled. */
438    private boolean verticalAxisTrace = false;
439
440    /** A vertical trace line. */
441    private transient Line2D verticalTraceLine;
442
443    /** A horizontal trace line. */
444    private transient Line2D horizontalTraceLine;
445
446    /** Menu item for zooming in on a chart (both axes). */
447    private JMenuItem zoomInBothMenuItem;
448
449    /** Menu item for zooming in on a chart (domain axis). */
450    private JMenuItem zoomInDomainMenuItem;
451
452    /** Menu item for zooming in on a chart (range axis). */
453    private JMenuItem zoomInRangeMenuItem;
454
455    /** Menu item for zooming out on a chart. */
456    private JMenuItem zoomOutBothMenuItem;
457
458    /** Menu item for zooming out on a chart (domain axis). */
459    private JMenuItem zoomOutDomainMenuItem;
460
461    /** Menu item for zooming out on a chart (range axis). */
462    private JMenuItem zoomOutRangeMenuItem;
463
464    /** Menu item for resetting the zoom (both axes). */
465    private JMenuItem zoomResetBothMenuItem;
466
467    /** Menu item for resetting the zoom (domain axis only). */
468    private JMenuItem zoomResetDomainMenuItem;
469
470    /** Menu item for resetting the zoom (range axis only). */
471    private JMenuItem zoomResetRangeMenuItem;
472
473    /**
474     * The default directory for saving charts to file.
475     *
476     * @since 1.0.7
477     */
478    private File defaultDirectoryForSaveAs;
479
480    /** A flag that controls whether or not file extensions are enforced. */
481    private boolean enforceFileExtensions;
482
483    /** A flag that indicates if original tooltip delays are changed. */
484    private boolean ownToolTipDelaysActive;
485
486    /** Original initial tooltip delay of ToolTipManager.sharedInstance(). */
487    private int originalToolTipInitialDelay;
488
489    /** Original reshow tooltip delay of ToolTipManager.sharedInstance(). */
490    private int originalToolTipReshowDelay;
491
492    /** Original dismiss tooltip delay of ToolTipManager.sharedInstance(). */
493    private int originalToolTipDismissDelay;
494
495    /** Own initial tooltip delay to be used in this chart panel. */
496    private int ownToolTipInitialDelay;
497
498    /** Own reshow tooltip delay to be used in this chart panel. */
499    private int ownToolTipReshowDelay;
500
501    /** Own dismiss tooltip delay to be used in this chart panel. */
502    private int ownToolTipDismissDelay;
503
504    /** The factor used to zoom in on an axis range. */
505    private double zoomInFactor = 0.5;
506
507    /** The factor used to zoom out on an axis range. */
508    private double zoomOutFactor = 2.0;
509
510    /**
511     * A flag that controls whether zoom operations are centred on the
512     * current anchor point, or the centre point of the relevant axis.
513     *
514     * @since 1.0.7
515     */
516    private boolean zoomAroundAnchor;
517
518    /**
519     * The paint used to draw the zoom rectangle outline.
520     *
521     * @since 1.0.13
522     */
523    private transient Paint zoomOutlinePaint;
524
525    /**
526     * The zoom fill paint (should use transparency).
527     *
528     * @since 1.0.13
529     */
530    private transient Paint zoomFillPaint;
531
532    /** The resourceBundle for the localization. */
533    protected static ResourceBundle localizationResources
534            = ResourceBundleWrapper.getBundle(
535                    "org.jfree.chart.LocalizationBundle");
536
537    /** 
538     * Temporary storage for the width and height of the chart 
539     * drawing area during panning.
540     */
541    private double panW, panH;
542
543    /** The last mouse position during panning. */
544    private Point panLast;
545
546    /**
547     * The mask for mouse events to trigger panning.
548     *
549     * @since 1.0.13
550     */
551    private int panMask = InputEvent.CTRL_MASK;
552
553    /**
554     * A list of overlays for the panel.
555     *
556     * @since 1.0.13
557     */
558    private List overlays;
559
560    /**
561     * Constructs a panel that displays the specified chart.
562     *
563     * @param chart  the chart.
564     */
565    public ChartPanel(JFreeChart chart) {
566
567        this(
568            chart,
569            DEFAULT_WIDTH,
570            DEFAULT_HEIGHT,
571            DEFAULT_MINIMUM_DRAW_WIDTH,
572            DEFAULT_MINIMUM_DRAW_HEIGHT,
573            DEFAULT_MAXIMUM_DRAW_WIDTH,
574            DEFAULT_MAXIMUM_DRAW_HEIGHT,
575            DEFAULT_BUFFER_USED,
576            true,  // properties
577            true,  // save
578            true,  // print
579            true,  // zoom
580            true   // tooltips
581        );
582
583    }
584
585    /**
586     * Constructs a panel containing a chart.  The <code>useBuffer</code> flag
587     * controls whether or not an offscreen <code>BufferedImage</code> is
588     * maintained for the chart.  If the buffer is used, more memory is
589     * consumed, but panel repaints will be a lot quicker in cases where the
590     * chart itself hasn't changed (for example, when another frame is moved
591     * to reveal the panel).  WARNING: If you set the <code>useBuffer</code>
592     * flag to false, note that the mouse zooming rectangle will (in that case)
593     * be drawn using XOR, and there is a SEVERE performance problem with that
594     * on JRE6 on Windows.
595     *
596     * @param chart  the chart.
597     * @param useBuffer  a flag controlling whether or not an off-screen buffer
598     *                   is used (read the warning above before setting this
599     *                   to <code>false</code>).
600     */
601    public ChartPanel(JFreeChart chart, boolean useBuffer) {
602
603        this(chart, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MINIMUM_DRAW_WIDTH,
604                DEFAULT_MINIMUM_DRAW_HEIGHT, DEFAULT_MAXIMUM_DRAW_WIDTH,
605                DEFAULT_MAXIMUM_DRAW_HEIGHT, useBuffer,
606                true,  // properties
607                true,  // save
608                true,  // print
609                true,  // zoom
610                true   // tooltips
611                );
612
613    }
614
615    /**
616     * Constructs a JFreeChart panel.
617     *
618     * @param chart  the chart.
619     * @param properties  a flag indicating whether or not the chart property
620     *                    editor should be available via the popup menu.
621     * @param save  a flag indicating whether or not save options should be
622     *              available via the popup menu.
623     * @param print  a flag indicating whether or not the print option
624     *               should be available via the popup menu.
625     * @param zoom  a flag indicating whether or not zoom options should
626     *              be added to the popup menu.
627     * @param tooltips  a flag indicating whether or not tooltips should be
628     *                  enabled for the chart.
629     */
630    public ChartPanel(JFreeChart chart,
631                      boolean properties,
632                      boolean save,
633                      boolean print,
634                      boolean zoom,
635                      boolean tooltips) {
636
637        this(chart,
638             DEFAULT_WIDTH,
639             DEFAULT_HEIGHT,
640             DEFAULT_MINIMUM_DRAW_WIDTH,
641             DEFAULT_MINIMUM_DRAW_HEIGHT,
642             DEFAULT_MAXIMUM_DRAW_WIDTH,
643             DEFAULT_MAXIMUM_DRAW_HEIGHT,
644             DEFAULT_BUFFER_USED,
645             properties,
646             save,
647             print,
648             zoom,
649             tooltips
650             );
651
652    }
653
654    /**
655     * Constructs a JFreeChart panel.
656     *
657     * @param chart  the chart.
658     * @param width  the preferred width of the panel.
659     * @param height  the preferred height of the panel.
660     * @param minimumDrawWidth  the minimum drawing width.
661     * @param minimumDrawHeight  the minimum drawing height.
662     * @param maximumDrawWidth  the maximum drawing width.
663     * @param maximumDrawHeight  the maximum drawing height.
664     * @param useBuffer  a flag that indicates whether to use the off-screen
665     *                   buffer to improve performance (at the expense of
666     *                   memory).
667     * @param properties  a flag indicating whether or not the chart property
668     *                    editor should be available via the popup menu.
669     * @param save  a flag indicating whether or not save options should be
670     *              available via the popup menu.
671     * @param print  a flag indicating whether or not the print option
672     *               should be available via the popup menu.
673     * @param zoom  a flag indicating whether or not zoom options should be
674     *              added to the popup menu.
675     * @param tooltips  a flag indicating whether or not tooltips should be
676     *                  enabled for the chart.
677     */
678    public ChartPanel(JFreeChart chart, int width, int height,
679            int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth,
680            int maximumDrawHeight, boolean useBuffer, boolean properties,
681            boolean save, boolean print, boolean zoom, boolean tooltips) {
682
683        this(chart, width, height, minimumDrawWidth, minimumDrawHeight,
684                maximumDrawWidth, maximumDrawHeight, useBuffer, properties,
685                true, save, print, zoom, tooltips);
686    }
687
688    /**
689     * Constructs a JFreeChart panel.
690     *
691     * @param chart  the chart.
692     * @param width  the preferred width of the panel.
693     * @param height  the preferred height of the panel.
694     * @param minimumDrawWidth  the minimum drawing width.
695     * @param minimumDrawHeight  the minimum drawing height.
696     * @param maximumDrawWidth  the maximum drawing width.
697     * @param maximumDrawHeight  the maximum drawing height.
698     * @param useBuffer  a flag that indicates whether to use the off-screen
699     *                   buffer to improve performance (at the expense of
700     *                   memory).
701     * @param properties  a flag indicating whether or not the chart property
702     *                    editor should be available via the popup menu.
703     * @param copy  a flag indicating whether or not a copy option should be
704     *              available via the popup menu.
705     * @param save  a flag indicating whether or not save options should be
706     *              available via the popup menu.
707     * @param print  a flag indicating whether or not the print option
708     *               should be available via the popup menu.
709     * @param zoom  a flag indicating whether or not zoom options should be
710     *              added to the popup menu.
711     * @param tooltips  a flag indicating whether or not tooltips should be
712     *                  enabled for the chart.
713     *
714     * @since 1.0.13
715     */
716    public ChartPanel(JFreeChart chart, int width, int height,
717           int minimumDrawWidth, int minimumDrawHeight, int maximumDrawWidth,
718           int maximumDrawHeight, boolean useBuffer, boolean properties,
719           boolean copy, boolean save, boolean print, boolean zoom,
720           boolean tooltips) {
721
722        setChart(chart);
723        this.chartMouseListeners = new EventListenerList();
724        this.info = new ChartRenderingInfo();
725        setPreferredSize(new Dimension(width, height));
726        this.useBuffer = useBuffer;
727        this.refreshBuffer = false;
728        this.minimumDrawWidth = minimumDrawWidth;
729        this.minimumDrawHeight = minimumDrawHeight;
730        this.maximumDrawWidth = maximumDrawWidth;
731        this.maximumDrawHeight = maximumDrawHeight;
732        this.zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE;
733
734        // set up popup menu...
735        this.popup = null;
736        if (properties || copy || save || print || zoom) {
737            this.popup = createPopupMenu(properties, copy, save, print, zoom);
738        }
739
740        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
741        enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
742        setDisplayToolTips(tooltips);
743        addMouseListener(this);
744        addMouseMotionListener(this);
745
746        this.defaultDirectoryForSaveAs = null;
747        this.enforceFileExtensions = true;
748
749        // initialize ChartPanel-specific tool tip delays with
750        // values the from ToolTipManager.sharedInstance()
751        ToolTipManager ttm = ToolTipManager.sharedInstance();
752        this.ownToolTipInitialDelay = ttm.getInitialDelay();
753        this.ownToolTipDismissDelay = ttm.getDismissDelay();
754        this.ownToolTipReshowDelay = ttm.getReshowDelay();
755
756        this.zoomAroundAnchor = false;
757        this.zoomOutlinePaint = Color.blue;
758        this.zoomFillPaint = new Color(0, 0, 255, 63);
759
760        this.panMask = InputEvent.CTRL_MASK;
761        // for MacOSX we can't use the CTRL key for mouse drags, see:
762        // http://developer.apple.com/qa/qa2004/qa1362.html
763        String osName = System.getProperty("os.name").toLowerCase();
764        if (osName.startsWith("mac os x")) {
765            this.panMask = InputEvent.ALT_MASK;
766        }
767
768        this.overlays = new java.util.ArrayList();
769    }
770
771    /**
772     * Returns the chart contained in the panel.
773     *
774     * @return The chart (possibly <code>null</code>).
775     */
776    public JFreeChart getChart() {
777        return this.chart;
778    }
779
780    /**
781     * Sets the chart that is displayed in the panel.
782     *
783     * @param chart  the chart (<code>null</code> permitted).
784     */
785    public void setChart(JFreeChart chart) {
786
787        // stop listening for changes to the existing chart
788        if (this.chart != null) {
789            this.chart.removeChangeListener(this);
790            this.chart.removeProgressListener(this);
791        }
792
793        // add the new chart
794        this.chart = chart;
795        if (chart != null) {
796            this.chart.addChangeListener(this);
797            this.chart.addProgressListener(this);
798            Plot plot = chart.getPlot();
799            this.domainZoomable = false;
800            this.rangeZoomable = false;
801            if (plot instanceof Zoomable) {
802                Zoomable z = (Zoomable) plot;
803                this.domainZoomable = z.isDomainZoomable();
804                this.rangeZoomable = z.isRangeZoomable();
805                this.orientation = z.getOrientation();
806            }
807        }
808        else {
809            this.domainZoomable = false;
810            this.rangeZoomable = false;
811        }
812        if (this.useBuffer) {
813            this.refreshBuffer = true;
814        }
815        repaint();
816
817    }
818
819    /**
820     * Returns the minimum drawing width for charts.
821     * <P>
822     * If the width available on the panel is less than this, then the chart is
823     * drawn at the minimum width then scaled down to fit.
824     *
825     * @return The minimum drawing width.
826     */
827    public int getMinimumDrawWidth() {
828        return this.minimumDrawWidth;
829    }
830
831    /**
832     * Sets the minimum drawing width for the chart on this panel.
833     * <P>
834     * At the time the chart is drawn on the panel, if the available width is
835     * less than this amount, the chart will be drawn using the minimum width
836     * then scaled down to fit the available space.
837     *
838     * @param width  The width.
839     */
840    public void setMinimumDrawWidth(int width) {
841        this.minimumDrawWidth = width;
842    }
843
844    /**
845     * Returns the maximum drawing width for charts.
846     * <P>
847     * If the width available on the panel is greater than this, then the chart
848     * is drawn at the maximum width then scaled up to fit.
849     *
850     * @return The maximum drawing width.
851     */
852    public int getMaximumDrawWidth() {
853        return this.maximumDrawWidth;
854    }
855
856    /**
857     * Sets the maximum drawing width for the chart on this panel.
858     * <P>
859     * At the time the chart is drawn on the panel, if the available width is
860     * greater than this amount, the chart will be drawn using the maximum
861     * width then scaled up to fit the available space.
862     *
863     * @param width  The width.
864     */
865    public void setMaximumDrawWidth(int width) {
866        this.maximumDrawWidth = width;
867    }
868
869    /**
870     * Returns the minimum drawing height for charts.
871     * <P>
872     * If the height available on the panel is less than this, then the chart
873     * is drawn at the minimum height then scaled down to fit.
874     *
875     * @return The minimum drawing height.
876     */
877    public int getMinimumDrawHeight() {
878        return this.minimumDrawHeight;
879    }
880
881    /**
882     * Sets the minimum drawing height for the chart on this panel.
883     * <P>
884     * At the time the chart is drawn on the panel, if the available height is
885     * less than this amount, the chart will be drawn using the minimum height
886     * then scaled down to fit the available space.
887     *
888     * @param height  The height.
889     */
890    public void setMinimumDrawHeight(int height) {
891        this.minimumDrawHeight = height;
892    }
893
894    /**
895     * Returns the maximum drawing height for charts.
896     * <P>
897     * If the height available on the panel is greater than this, then the
898     * chart is drawn at the maximum height then scaled up to fit.
899     *
900     * @return The maximum drawing height.
901     */
902    public int getMaximumDrawHeight() {
903        return this.maximumDrawHeight;
904    }
905
906    /**
907     * Sets the maximum drawing height for the chart on this panel.
908     * <P>
909     * At the time the chart is drawn on the panel, if the available height is
910     * greater than this amount, the chart will be drawn using the maximum
911     * height then scaled up to fit the available space.
912     *
913     * @param height  The height.
914     */
915    public void setMaximumDrawHeight(int height) {
916        this.maximumDrawHeight = height;
917    }
918
919    /**
920     * Returns the X scale factor for the chart.  This will be 1.0 if no
921     * scaling has been used.
922     *
923     * @return The scale factor.
924     */
925    public double getScaleX() {
926        return this.scaleX;
927    }
928
929    /**
930     * Returns the Y scale factory for the chart.  This will be 1.0 if no
931     * scaling has been used.
932     *
933     * @return The scale factor.
934     */
935    public double getScaleY() {
936        return this.scaleY;
937    }
938
939    /**
940     * Returns the anchor point.
941     *
942     * @return The anchor point (possibly <code>null</code>).
943     */
944    public Point2D getAnchor() {
945        return this.anchor;
946    }
947
948    /**
949     * Sets the anchor point.  This method is provided for the use of
950     * subclasses, not end users.
951     *
952     * @param anchor  the anchor point (<code>null</code> permitted).
953     */
954    protected void setAnchor(Point2D anchor) {
955        this.anchor = anchor;
956    }
957
958    /**
959     * Returns the popup menu.
960     *
961     * @return The popup menu.
962     */
963    public JPopupMenu getPopupMenu() {
964        return this.popup;
965    }
966
967    /**
968     * Sets the popup menu for the panel.
969     *
970     * @param popup  the popup menu (<code>null</code> permitted).
971     */
972    public void setPopupMenu(JPopupMenu popup) {
973        this.popup = popup;
974    }
975
976    /**
977     * Returns the chart rendering info from the most recent chart redraw.
978     *
979     * @return The chart rendering info.
980     */
981    public ChartRenderingInfo getChartRenderingInfo() {
982        return this.info;
983    }
984
985    /**
986     * A convenience method that switches on mouse-based zooming.
987     *
988     * @param flag  <code>true</code> enables zooming and rectangle fill on
989     *              zoom.
990     */
991    public void setMouseZoomable(boolean flag) {
992        setMouseZoomable(flag, true);
993    }
994
995    /**
996     * A convenience method that switches on mouse-based zooming.
997     *
998     * @param flag  <code>true</code> if zooming enabled
999     * @param fillRectangle  <code>true</code> if zoom rectangle is filled,
1000     *                       false if rectangle is shown as outline only.
1001     */
1002    public void setMouseZoomable(boolean flag, boolean fillRectangle) {
1003        setDomainZoomable(flag);
1004        setRangeZoomable(flag);
1005        setFillZoomRectangle(fillRectangle);
1006    }
1007
1008    /**
1009     * Returns the flag that determines whether or not zooming is enabled for
1010     * the domain axis.
1011     *
1012     * @return A boolean.
1013     */
1014    public boolean isDomainZoomable() {
1015        return this.domainZoomable;
1016    }
1017
1018    /**
1019     * Sets the flag that controls whether or not zooming is enable for the
1020     * domain axis.  A check is made to ensure that the current plot supports
1021     * zooming for the domain values.
1022     *
1023     * @param flag  <code>true</code> enables zooming if possible.
1024     */
1025    public void setDomainZoomable(boolean flag) {
1026        if (flag) {
1027            Plot plot = this.chart.getPlot();
1028            if (plot instanceof Zoomable) {
1029                Zoomable z = (Zoomable) plot;
1030                this.domainZoomable = flag && (z.isDomainZoomable());
1031            }
1032        }
1033        else {
1034            this.domainZoomable = false;
1035        }
1036    }
1037
1038    /**
1039     * Returns the flag that determines whether or not zooming is enabled for
1040     * the range axis.
1041     *
1042     * @return A boolean.
1043     */
1044    public boolean isRangeZoomable() {
1045        return this.rangeZoomable;
1046    }
1047
1048    /**
1049     * A flag that controls mouse-based zooming on the vertical axis.
1050     *
1051     * @param flag  <code>true</code> enables zooming.
1052     */
1053    public void setRangeZoomable(boolean flag) {
1054        if (flag) {
1055            Plot plot = this.chart.getPlot();
1056            if (plot instanceof Zoomable) {
1057                Zoomable z = (Zoomable) plot;
1058                this.rangeZoomable = flag && (z.isRangeZoomable());
1059            }
1060        }
1061        else {
1062            this.rangeZoomable = false;
1063        }
1064    }
1065
1066    /**
1067     * Returns the flag that controls whether or not the zoom rectangle is
1068     * filled when drawn.
1069     *
1070     * @return A boolean.
1071     */
1072    public boolean getFillZoomRectangle() {
1073        return this.fillZoomRectangle;
1074    }
1075
1076    /**
1077     * A flag that controls how the zoom rectangle is drawn.
1078     *
1079     * @param flag  <code>true</code> instructs to fill the rectangle on
1080     *              zoom, otherwise it will be outlined.
1081     */
1082    public void setFillZoomRectangle(boolean flag) {
1083        this.fillZoomRectangle = flag;
1084    }
1085
1086    /**
1087     * Returns the zoom trigger distance.  This controls how far the mouse must
1088     * move before a zoom action is triggered.
1089     *
1090     * @return The distance (in Java2D units).
1091     */
1092    public int getZoomTriggerDistance() {
1093        return this.zoomTriggerDistance;
1094    }
1095
1096    /**
1097     * Sets the zoom trigger distance.  This controls how far the mouse must
1098     * move before a zoom action is triggered.
1099     *
1100     * @param distance  the distance (in Java2D units).
1101     */
1102    public void setZoomTriggerDistance(int distance) {
1103        this.zoomTriggerDistance = distance;
1104    }
1105
1106    /**
1107     * Returns the flag that controls whether or not a horizontal axis trace
1108     * line is drawn over the plot area at the current mouse location.
1109     *
1110     * @return A boolean.
1111     */
1112    public boolean getHorizontalAxisTrace() {
1113        return this.horizontalAxisTrace;
1114    }
1115
1116    /**
1117     * A flag that controls trace lines on the horizontal axis.
1118     *
1119     * @param flag  <code>true</code> enables trace lines for the mouse
1120     *      pointer on the horizontal axis.
1121     */
1122    public void setHorizontalAxisTrace(boolean flag) {
1123        this.horizontalAxisTrace = flag;
1124    }
1125
1126    /**
1127     * Returns the horizontal trace line.
1128     *
1129     * @return The horizontal trace line (possibly <code>null</code>).
1130     */
1131    protected Line2D getHorizontalTraceLine() {
1132        return this.horizontalTraceLine;
1133    }
1134
1135    /**
1136     * Sets the horizontal trace line.
1137     *
1138     * @param line  the line (<code>null</code> permitted).
1139     */
1140    protected void setHorizontalTraceLine(Line2D line) {
1141        this.horizontalTraceLine = line;
1142    }
1143
1144    /**
1145     * Returns the flag that controls whether or not a vertical axis trace
1146     * line is drawn over the plot area at the current mouse location.
1147     *
1148     * @return A boolean.
1149     */
1150    public boolean getVerticalAxisTrace() {
1151        return this.verticalAxisTrace;
1152    }
1153
1154    /**
1155     * A flag that controls trace lines on the vertical axis.
1156     *
1157     * @param flag  <code>true</code> enables trace lines for the mouse
1158     *              pointer on the vertical axis.
1159     */
1160    public void setVerticalAxisTrace(boolean flag) {
1161        this.verticalAxisTrace = flag;
1162    }
1163
1164    /**
1165     * Returns the vertical trace line.
1166     *
1167     * @return The vertical trace line (possibly <code>null</code>).
1168     */
1169    protected Line2D getVerticalTraceLine() {
1170        return this.verticalTraceLine;
1171    }
1172
1173    /**
1174     * Sets the vertical trace line.
1175     *
1176     * @param line  the line (<code>null</code> permitted).
1177     */
1178    protected void setVerticalTraceLine(Line2D line) {
1179        this.verticalTraceLine = line;
1180    }
1181
1182    /**
1183     * Returns the default directory for the "save as" option.
1184     *
1185     * @return The default directory (possibly <code>null</code>).
1186     *
1187     * @since 1.0.7
1188     */
1189    public File getDefaultDirectoryForSaveAs() {
1190        return this.defaultDirectoryForSaveAs;
1191    }
1192
1193    /**
1194     * Sets the default directory for the "save as" option.  If you set this
1195     * to <code>null</code>, the user's default directory will be used.
1196     *
1197     * @param directory  the directory (<code>null</code> permitted).
1198     *
1199     * @since 1.0.7
1200     */
1201    public void setDefaultDirectoryForSaveAs(File directory) {
1202        if (directory != null) {
1203            if (!directory.isDirectory()) {
1204                throw new IllegalArgumentException(
1205                        "The 'directory' argument is not a directory.");
1206            }
1207        }
1208        this.defaultDirectoryForSaveAs = directory;
1209    }
1210
1211    /**
1212     * Returns <code>true</code> if file extensions should be enforced, and
1213     * <code>false</code> otherwise.
1214     *
1215     * @return The flag.
1216     *
1217     * @see #setEnforceFileExtensions(boolean)
1218     */
1219    public boolean isEnforceFileExtensions() {
1220        return this.enforceFileExtensions;
1221    }
1222
1223    /**
1224     * Sets a flag that controls whether or not file extensions are enforced.
1225     *
1226     * @param enforce  the new flag value.
1227     *
1228     * @see #isEnforceFileExtensions()
1229     */
1230    public void setEnforceFileExtensions(boolean enforce) {
1231        this.enforceFileExtensions = enforce;
1232    }
1233
1234    /**
1235     * Returns the flag that controls whether or not zoom operations are
1236     * centered around the current anchor point.
1237     *
1238     * @return A boolean.
1239     *
1240     * @since 1.0.7
1241     *
1242     * @see #setZoomAroundAnchor(boolean)
1243     */
1244    public boolean getZoomAroundAnchor() {
1245        return this.zoomAroundAnchor;
1246    }
1247
1248    /**
1249     * Sets the flag that controls whether or not zoom operations are
1250     * centered around the current anchor point.
1251     *
1252     * @param zoomAroundAnchor  the new flag value.
1253     *
1254     * @since 1.0.7
1255     *
1256     * @see #getZoomAroundAnchor()
1257     */
1258    public void setZoomAroundAnchor(boolean zoomAroundAnchor) {
1259        this.zoomAroundAnchor = zoomAroundAnchor;
1260    }
1261
1262    /**
1263     * Returns the zoom rectangle fill paint.
1264     *
1265     * @return The zoom rectangle fill paint (never <code>null</code>).
1266     *
1267     * @see #setZoomFillPaint(java.awt.Paint)
1268     * @see #setFillZoomRectangle(boolean)
1269     *
1270     * @since 1.0.13
1271     */
1272    public Paint getZoomFillPaint() {
1273        return this.zoomFillPaint;
1274    }
1275
1276    /**
1277     * Sets the zoom rectangle fill paint.
1278     *
1279     * @param paint  the paint (<code>null</code> not permitted).
1280     *
1281     * @see #getZoomFillPaint()
1282     * @see #getFillZoomRectangle()
1283     *
1284     * @since 1.0.13
1285     */
1286    public void setZoomFillPaint(Paint paint) {
1287        ParamChecks.nullNotPermitted(paint, "paint");
1288        this.zoomFillPaint = paint;
1289    }
1290
1291    /**
1292     * Returns the zoom rectangle outline paint.
1293     *
1294     * @return The zoom rectangle outline paint (never <code>null</code>).
1295     *
1296     * @see #setZoomOutlinePaint(java.awt.Paint)
1297     * @see #setFillZoomRectangle(boolean)
1298     *
1299     * @since 1.0.13
1300     */
1301    public Paint getZoomOutlinePaint() {
1302        return this.zoomOutlinePaint;
1303    }
1304
1305    /**
1306     * Sets the zoom rectangle outline paint.
1307     *
1308     * @param paint  the paint (<code>null</code> not permitted).
1309     *
1310     * @see #getZoomOutlinePaint()
1311     * @see #getFillZoomRectangle()
1312     *
1313     * @since 1.0.13
1314     */
1315    public void setZoomOutlinePaint(Paint paint) {
1316        this.zoomOutlinePaint = paint;
1317    }
1318
1319    /**
1320     * The mouse wheel handler.
1321     */
1322    private MouseWheelHandler mouseWheelHandler;
1323
1324    /**
1325     * Returns <code>true</code> if the mouse wheel handler is enabled, and
1326     * <code>false</code> otherwise.
1327     *
1328     * @return A boolean.
1329     *
1330     * @since 1.0.13
1331     */
1332    public boolean isMouseWheelEnabled() {
1333        return this.mouseWheelHandler != null;
1334    }
1335
1336    /**
1337     * Enables or disables mouse wheel support for the panel.
1338     *
1339     * @param flag  a boolean.
1340     *
1341     * @since 1.0.13
1342     */
1343    public void setMouseWheelEnabled(boolean flag) {
1344        if (flag && this.mouseWheelHandler == null) {
1345            this.mouseWheelHandler = new MouseWheelHandler(this);
1346        }
1347        else if (!flag && this.mouseWheelHandler != null) {
1348            this.removeMouseWheelListener(this.mouseWheelHandler);
1349            this.mouseWheelHandler = null;
1350        } 
1351    }
1352
1353    /**
1354     * Add an overlay to the panel.
1355     *
1356     * @param overlay  the overlay (<code>null</code> not permitted).
1357     *
1358     * @since 1.0.13
1359     */
1360    public void addOverlay(Overlay overlay) {
1361        ParamChecks.nullNotPermitted(overlay, "overlay");
1362        this.overlays.add(overlay);
1363        overlay.addChangeListener(this);
1364        repaint();
1365    }
1366
1367    /**
1368     * Removes an overlay from the panel.
1369     *
1370     * @param overlay  the overlay to remove (<code>null</code> not permitted).
1371     *
1372     * @since 1.0.13
1373     */
1374    public void removeOverlay(Overlay overlay) {
1375        ParamChecks.nullNotPermitted(overlay, "overlay");
1376        boolean removed = this.overlays.remove(overlay);
1377        if (removed) {
1378            overlay.removeChangeListener(this);
1379            repaint();
1380        }
1381    }
1382
1383    /**
1384     * Handles a change to an overlay by repainting the panel.
1385     *
1386     * @param event  the event.
1387     *
1388     * @since 1.0.13
1389     */
1390    @Override
1391    public void overlayChanged(OverlayChangeEvent event) {
1392        repaint();
1393    }
1394
1395    /**
1396     * Switches the display of tooltips for the panel on or off.  Note that
1397     * tooltips can only be displayed if the chart has been configured to
1398     * generate tooltip items.
1399     *
1400     * @param flag  <code>true</code> to enable tooltips, <code>false</code> to
1401     *              disable tooltips.
1402     */
1403    public void setDisplayToolTips(boolean flag) {
1404        if (flag) {
1405            ToolTipManager.sharedInstance().registerComponent(this);
1406        }
1407        else {
1408            ToolTipManager.sharedInstance().unregisterComponent(this);
1409        }
1410    }
1411
1412    /**
1413     * Returns a string for the tooltip.
1414     *
1415     * @param e  the mouse event.
1416     *
1417     * @return A tool tip or <code>null</code> if no tooltip is available.
1418     */
1419    @Override
1420    public String getToolTipText(MouseEvent e) {
1421        String result = null;
1422        if (this.info != null) {
1423            EntityCollection entities = this.info.getEntityCollection();
1424            if (entities != null) {
1425                Insets insets = getInsets();
1426                ChartEntity entity = entities.getEntity(
1427                        (int) ((e.getX() - insets.left) / this.scaleX),
1428                        (int) ((e.getY() - insets.top) / this.scaleY));
1429                if (entity != null) {
1430                    result = entity.getToolTipText();
1431                }
1432            }
1433        }
1434        return result;
1435    }
1436
1437    /**
1438     * Translates a Java2D point on the chart to a screen location.
1439     *
1440     * @param java2DPoint  the Java2D point.
1441     *
1442     * @return The screen location.
1443     */
1444    public Point translateJava2DToScreen(Point2D java2DPoint) {
1445        Insets insets = getInsets();
1446        int x = (int) (java2DPoint.getX() * this.scaleX + insets.left);
1447        int y = (int) (java2DPoint.getY() * this.scaleY + insets.top);
1448        return new Point(x, y);
1449    }
1450
1451    /**
1452     * Translates a panel (component) location to a Java2D point.
1453     *
1454     * @param screenPoint  the screen location (<code>null</code> not
1455     *                     permitted).
1456     *
1457     * @return The Java2D coordinates.
1458     */
1459    public Point2D translateScreenToJava2D(Point screenPoint) {
1460        Insets insets = getInsets();
1461        double x = (screenPoint.getX() - insets.left) / this.scaleX;
1462        double y = (screenPoint.getY() - insets.top) / this.scaleY;
1463        return new Point2D.Double(x, y);
1464    }
1465
1466    /**
1467     * Applies any scaling that is in effect for the chart drawing to the
1468     * given rectangle.
1469     *
1470     * @param rect  the rectangle (<code>null</code> not permitted).
1471     *
1472     * @return A new scaled rectangle.
1473     */
1474    public Rectangle2D scale(Rectangle2D rect) {
1475        Insets insets = getInsets();
1476        double x = rect.getX() * getScaleX() + insets.left;
1477        double y = rect.getY() * getScaleY() + insets.top;
1478        double w = rect.getWidth() * getScaleX();
1479        double h = rect.getHeight() * getScaleY();
1480        return new Rectangle2D.Double(x, y, w, h);
1481    }
1482
1483    /**
1484     * Returns the chart entity at a given point.
1485     * <P>
1486     * This method will return null if there is (a) no entity at the given
1487     * point, or (b) no entity collection has been generated.
1488     *
1489     * @param viewX  the x-coordinate.
1490     * @param viewY  the y-coordinate.
1491     *
1492     * @return The chart entity (possibly <code>null</code>).
1493     */
1494    public ChartEntity getEntityForPoint(int viewX, int viewY) {
1495
1496        ChartEntity result = null;
1497        if (this.info != null) {
1498            Insets insets = getInsets();
1499            double x = (viewX - insets.left) / this.scaleX;
1500            double y = (viewY - insets.top) / this.scaleY;
1501            EntityCollection entities = this.info.getEntityCollection();
1502            result = entities != null ? entities.getEntity(x, y) : null;
1503        }
1504        return result;
1505
1506    }
1507
1508    /**
1509     * Returns the flag that controls whether or not the offscreen buffer
1510     * needs to be refreshed.
1511     *
1512     * @return A boolean.
1513     */
1514    public boolean getRefreshBuffer() {
1515        return this.refreshBuffer;
1516    }
1517
1518    /**
1519     * Sets the refresh buffer flag.  This flag is used to avoid unnecessary
1520     * redrawing of the chart when the offscreen image buffer is used.
1521     *
1522     * @param flag  <code>true</code> indicates that the buffer should be
1523     *              refreshed.
1524     */
1525    public void setRefreshBuffer(boolean flag) {
1526        this.refreshBuffer = flag;
1527    }
1528
1529    /**
1530     * Paints the component by drawing the chart to fill the entire component,
1531     * but allowing for the insets (which will be non-zero if a border has been
1532     * set for this component).  To increase performance (at the expense of
1533     * memory), an off-screen buffer image can be used.
1534     *
1535     * @param g  the graphics device for drawing on.
1536     */
1537    @Override
1538    public void paintComponent(Graphics g) {
1539        super.paintComponent(g);
1540        if (this.chart == null) {
1541            return;
1542        }
1543        Graphics2D g2 = (Graphics2D) g.create();
1544
1545        // first determine the size of the chart rendering area...
1546        Dimension size = getSize();
1547        Insets insets = getInsets();
1548        Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top,
1549                size.getWidth() - insets.left - insets.right,
1550                size.getHeight() - insets.top - insets.bottom);
1551
1552        // work out if scaling is required...
1553        boolean scale = false;
1554        double drawWidth = available.getWidth();
1555        double drawHeight = available.getHeight();
1556        this.scaleX = 1.0;
1557        this.scaleY = 1.0;
1558
1559        if (drawWidth < this.minimumDrawWidth) {
1560            this.scaleX = drawWidth / this.minimumDrawWidth;
1561            drawWidth = this.minimumDrawWidth;
1562            scale = true;
1563        }
1564        else if (drawWidth > this.maximumDrawWidth) {
1565            this.scaleX = drawWidth / this.maximumDrawWidth;
1566            drawWidth = this.maximumDrawWidth;
1567            scale = true;
1568        }
1569
1570        if (drawHeight < this.minimumDrawHeight) {
1571            this.scaleY = drawHeight / this.minimumDrawHeight;
1572            drawHeight = this.minimumDrawHeight;
1573            scale = true;
1574        }
1575        else if (drawHeight > this.maximumDrawHeight) {
1576            this.scaleY = drawHeight / this.maximumDrawHeight;
1577            drawHeight = this.maximumDrawHeight;
1578            scale = true;
1579        }
1580
1581        Rectangle2D chartArea = new Rectangle2D.Double(0.0, 0.0, drawWidth,
1582                drawHeight);
1583
1584        // are we using the chart buffer?
1585        if (this.useBuffer) {
1586
1587            // do we need to resize the buffer?
1588            if ((this.chartBuffer == null)
1589                    || (this.chartBufferWidth != available.getWidth())
1590                    || (this.chartBufferHeight != available.getHeight())) {
1591                this.chartBufferWidth = (int) available.getWidth();
1592                this.chartBufferHeight = (int) available.getHeight();
1593                GraphicsConfiguration gc = g2.getDeviceConfiguration();
1594                this.chartBuffer = gc.createCompatibleImage(
1595                        this.chartBufferWidth, this.chartBufferHeight,
1596                        Transparency.TRANSLUCENT);
1597                this.refreshBuffer = true;
1598            }
1599
1600            // do we need to redraw the buffer?
1601            if (this.refreshBuffer) {
1602
1603                this.refreshBuffer = false; // clear the flag
1604
1605                Rectangle2D bufferArea = new Rectangle2D.Double(
1606                        0, 0, this.chartBufferWidth, this.chartBufferHeight);
1607
1608                // make the background of the buffer clear and transparent
1609                Graphics2D bufferG2 = (Graphics2D)
1610                        this.chartBuffer.getGraphics();
1611                Composite savedComposite = bufferG2.getComposite();
1612                bufferG2.setComposite(AlphaComposite.getInstance(
1613                        AlphaComposite.CLEAR, 0.0f));
1614                Rectangle r = new Rectangle(0, 0, this.chartBufferWidth,
1615                        this.chartBufferHeight);
1616                bufferG2.fill(r);
1617                bufferG2.setComposite(savedComposite);
1618                
1619                if (scale) {
1620                    AffineTransform saved = bufferG2.getTransform();
1621                    AffineTransform st = AffineTransform.getScaleInstance(
1622                            this.scaleX, this.scaleY);
1623                    bufferG2.transform(st);
1624                    this.chart.draw(bufferG2, chartArea, this.anchor,
1625                            this.info);
1626                    bufferG2.setTransform(saved);
1627                }
1628                else {
1629                    this.chart.draw(bufferG2, bufferArea, this.anchor,
1630                            this.info);
1631                }
1632
1633            }
1634
1635            // zap the buffer onto the panel...
1636            g2.drawImage(this.chartBuffer, insets.left, insets.top, this);
1637
1638        }
1639
1640        // or redrawing the chart every time...
1641        else {
1642
1643            AffineTransform saved = g2.getTransform();
1644            g2.translate(insets.left, insets.top);
1645            if (scale) {
1646                AffineTransform st = AffineTransform.getScaleInstance(
1647                        this.scaleX, this.scaleY);
1648                g2.transform(st);
1649            }
1650            this.chart.draw(g2, chartArea, this.anchor, this.info);
1651            g2.setTransform(saved);
1652
1653        }
1654
1655        Iterator iterator = this.overlays.iterator();
1656        while (iterator.hasNext()) {
1657            Overlay overlay = (Overlay) iterator.next();
1658            overlay.paintOverlay(g2, this);
1659        }
1660
1661        // redraw the zoom rectangle (if present) - if useBuffer is false,
1662        // we use XOR so we can XOR the rectangle away again without redrawing
1663        // the chart
1664        drawZoomRectangle(g2, !this.useBuffer);
1665
1666        g2.dispose();
1667
1668        this.anchor = null;
1669        this.verticalTraceLine = null;
1670        this.horizontalTraceLine = null;
1671
1672    }
1673
1674    /**
1675     * Receives notification of changes to the chart, and redraws the chart.
1676     *
1677     * @param event  details of the chart change event.
1678     */
1679    @Override
1680    public void chartChanged(ChartChangeEvent event) {
1681        this.refreshBuffer = true;
1682        Plot plot = this.chart.getPlot();
1683        if (plot instanceof Zoomable) {
1684            Zoomable z = (Zoomable) plot;
1685            this.orientation = z.getOrientation();
1686        }
1687        repaint();
1688    }
1689
1690    /**
1691     * Receives notification of a chart progress event.
1692     *
1693     * @param event  the event.
1694     */
1695    @Override
1696    public void chartProgress(ChartProgressEvent event) {
1697        // does nothing - override if necessary
1698    }
1699
1700    /**
1701     * Handles action events generated by the popup menu.
1702     *
1703     * @param event  the event.
1704     */
1705    @Override
1706    public void actionPerformed(ActionEvent event) {
1707
1708        String command = event.getActionCommand();
1709
1710        // many of the zoom methods need a screen location - all we have is
1711        // the zoomPoint, but it might be null.  Here we grab the x and y
1712        // coordinates, or use defaults...
1713        double screenX = -1.0;
1714        double screenY = -1.0;
1715        if (this.zoomPoint != null) {
1716            screenX = this.zoomPoint.getX();
1717            screenY = this.zoomPoint.getY();
1718        }
1719
1720        if (command.equals(PROPERTIES_COMMAND)) {
1721            doEditChartProperties();
1722        }
1723        else if (command.equals(COPY_COMMAND)) {
1724            doCopy();
1725        }
1726        else if (command.equals(SAVE_AS_PNG_COMMAND)) {
1727            try {
1728                doSaveAs();
1729            }
1730            catch (IOException e) {
1731                JOptionPane.showMessageDialog(this, "I/O error occurred.", 
1732                        "Save As PNG", JOptionPane.WARNING_MESSAGE);
1733            }
1734        }
1735        else if (command.equals(SAVE_AS_SVG_COMMAND)) {
1736            try {
1737                saveAsSVG(null);
1738            } catch (IOException e) {
1739                JOptionPane.showMessageDialog(this, "I/O error occurred.", 
1740                        "Save As SVG", JOptionPane.WARNING_MESSAGE);
1741            }
1742        }
1743        else if (command.equals(SAVE_AS_PDF_COMMAND)) {
1744            saveAsPDF(null);
1745        }
1746        else if (command.equals(PRINT_COMMAND)) {
1747            createChartPrintJob();
1748        }
1749        else if (command.equals(ZOOM_IN_BOTH_COMMAND)) {
1750            zoomInBoth(screenX, screenY);
1751        }
1752        else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) {
1753            zoomInDomain(screenX, screenY);
1754        }
1755        else if (command.equals(ZOOM_IN_RANGE_COMMAND)) {
1756            zoomInRange(screenX, screenY);
1757        }
1758        else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) {
1759            zoomOutBoth(screenX, screenY);
1760        }
1761        else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) {
1762            zoomOutDomain(screenX, screenY);
1763        }
1764        else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) {
1765            zoomOutRange(screenX, screenY);
1766        }
1767        else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) {
1768            restoreAutoBounds();
1769        }
1770        else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) {
1771            restoreAutoDomainBounds();
1772        }
1773        else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) {
1774            restoreAutoRangeBounds();
1775        }
1776
1777    }
1778
1779    /**
1780     * Handles a 'mouse entered' event. This method changes the tooltip delays
1781     * of ToolTipManager.sharedInstance() to the possibly different values set
1782     * for this chart panel.
1783     *
1784     * @param e  the mouse event.
1785     */
1786    @Override
1787    public void mouseEntered(MouseEvent e) {
1788        if (!this.ownToolTipDelaysActive) {
1789            ToolTipManager ttm = ToolTipManager.sharedInstance();
1790
1791            this.originalToolTipInitialDelay = ttm.getInitialDelay();
1792            ttm.setInitialDelay(this.ownToolTipInitialDelay);
1793
1794            this.originalToolTipReshowDelay = ttm.getReshowDelay();
1795            ttm.setReshowDelay(this.ownToolTipReshowDelay);
1796
1797            this.originalToolTipDismissDelay = ttm.getDismissDelay();
1798            ttm.setDismissDelay(this.ownToolTipDismissDelay);
1799
1800            this.ownToolTipDelaysActive = true;
1801        }
1802    }
1803
1804    /**
1805     * Handles a 'mouse exited' event. This method resets the tooltip delays of
1806     * ToolTipManager.sharedInstance() to their
1807     * original values in effect before mouseEntered()
1808     *
1809     * @param e  the mouse event.
1810     */
1811    @Override
1812    public void mouseExited(MouseEvent e) {
1813        if (this.ownToolTipDelaysActive) {
1814            // restore original tooltip dealys
1815            ToolTipManager ttm = ToolTipManager.sharedInstance();
1816            ttm.setInitialDelay(this.originalToolTipInitialDelay);
1817            ttm.setReshowDelay(this.originalToolTipReshowDelay);
1818            ttm.setDismissDelay(this.originalToolTipDismissDelay);
1819            this.ownToolTipDelaysActive = false;
1820        }
1821    }
1822
1823    /**
1824     * Handles a 'mouse pressed' event.
1825     * <P>
1826     * This event is the popup trigger on Unix/Linux.  For Windows, the popup
1827     * trigger is the 'mouse released' event.
1828     *
1829     * @param e  The mouse event.
1830     */
1831    @Override
1832    public void mousePressed(MouseEvent e) {
1833        if (this.chart == null) {
1834            return;
1835        }
1836        Plot plot = this.chart.getPlot();
1837        int mods = e.getModifiers();
1838        if ((mods & this.panMask) == this.panMask) {
1839            // can we pan this plot?
1840            if (plot instanceof Pannable) {
1841                Pannable pannable = (Pannable) plot;
1842                if (pannable.isDomainPannable() || pannable.isRangePannable()) {
1843                    Rectangle2D screenDataArea = getScreenDataArea(e.getX(),
1844                            e.getY());
1845                    if (screenDataArea != null && screenDataArea.contains(
1846                            e.getPoint())) {
1847                        this.panW = screenDataArea.getWidth();
1848                        this.panH = screenDataArea.getHeight();
1849                        this.panLast = e.getPoint();
1850                        setCursor(Cursor.getPredefinedCursor(
1851                                Cursor.MOVE_CURSOR));
1852                    }
1853                }
1854                // the actual panning occurs later in the mouseDragged() 
1855                // method
1856            }
1857        }
1858        else if (this.zoomRectangle == null) {
1859            Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
1860            if (screenDataArea != null) {
1861                this.zoomPoint = getPointInRectangle(e.getX(), e.getY(),
1862                        screenDataArea);
1863            }
1864            else {
1865                this.zoomPoint = null;
1866            }
1867            if (e.isPopupTrigger()) {
1868                if (this.popup != null) {
1869                    displayPopupMenu(e.getX(), e.getY());
1870                }
1871            }
1872        }
1873    }
1874
1875    /**
1876     * Returns a point based on (x, y) but constrained to be within the bounds
1877     * of the given rectangle.  This method could be moved to JCommon.
1878     *
1879     * @param x  the x-coordinate.
1880     * @param y  the y-coordinate.
1881     * @param area  the rectangle (<code>null</code> not permitted).
1882     *
1883     * @return A point within the rectangle.
1884     */
1885    private Point2D getPointInRectangle(int x, int y, Rectangle2D area) {
1886        double xx = Math.max(area.getMinX(), Math.min(x, area.getMaxX()));
1887        double yy = Math.max(area.getMinY(), Math.min(y, area.getMaxY()));
1888        return new Point2D.Double(xx, yy);
1889    }
1890
1891    /**
1892     * Handles a 'mouse dragged' event.
1893     *
1894     * @param e  the mouse event.
1895     */
1896    @Override
1897    public void mouseDragged(MouseEvent e) {
1898
1899        // if the popup menu has already been triggered, then ignore dragging...
1900        if (this.popup != null && this.popup.isShowing()) {
1901            return;
1902        }
1903
1904        // handle panning if we have a start point
1905        if (this.panLast != null) {
1906            double dx = e.getX() - this.panLast.getX();
1907            double dy = e.getY() - this.panLast.getY();
1908            if (dx == 0.0 && dy == 0.0) {
1909                return;
1910            }
1911            double wPercent = -dx / this.panW;
1912            double hPercent = dy / this.panH;
1913            boolean old = this.chart.getPlot().isNotify();
1914            this.chart.getPlot().setNotify(false);
1915            Pannable p = (Pannable) this.chart.getPlot();
1916            if (p.getOrientation() == PlotOrientation.VERTICAL) {
1917                p.panDomainAxes(wPercent, this.info.getPlotInfo(),
1918                        this.panLast);
1919                p.panRangeAxes(hPercent, this.info.getPlotInfo(),
1920                        this.panLast);
1921            }
1922            else {
1923                p.panDomainAxes(hPercent, this.info.getPlotInfo(),
1924                        this.panLast);
1925                p.panRangeAxes(wPercent, this.info.getPlotInfo(),
1926                        this.panLast);
1927            }
1928            this.panLast = e.getPoint();
1929            this.chart.getPlot().setNotify(old);
1930            return;
1931        }
1932
1933        // if no initial zoom point was set, ignore dragging...
1934        if (this.zoomPoint == null) {
1935            return;
1936        }
1937        Graphics2D g2 = (Graphics2D) getGraphics();
1938
1939        // erase the previous zoom rectangle (if any).  We only need to do
1940        // this is we are using XOR mode, which we do when we're not using
1941        // the buffer (if there is a buffer, then at the end of this method we
1942        // just trigger a repaint)
1943        if (!this.useBuffer) {
1944            drawZoomRectangle(g2, true);
1945        }
1946
1947        boolean hZoom, vZoom;
1948        if (this.orientation == PlotOrientation.HORIZONTAL) {
1949            hZoom = this.rangeZoomable;
1950            vZoom = this.domainZoomable;
1951        }
1952        else {
1953            hZoom = this.domainZoomable;
1954            vZoom = this.rangeZoomable;
1955        }
1956        Rectangle2D scaledDataArea = getScreenDataArea(
1957                (int) this.zoomPoint.getX(), (int) this.zoomPoint.getY());
1958        if (hZoom && vZoom) {
1959            // selected rectangle shouldn't extend outside the data area...
1960            double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
1961            double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
1962            this.zoomRectangle = new Rectangle2D.Double(
1963                    this.zoomPoint.getX(), this.zoomPoint.getY(),
1964                    xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY());
1965        }
1966        else if (hZoom) {
1967            double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
1968            this.zoomRectangle = new Rectangle2D.Double(
1969                    this.zoomPoint.getX(), scaledDataArea.getMinY(),
1970                    xmax - this.zoomPoint.getX(), scaledDataArea.getHeight());
1971        }
1972        else if (vZoom) {
1973            double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
1974            this.zoomRectangle = new Rectangle2D.Double(
1975                    scaledDataArea.getMinX(), this.zoomPoint.getY(),
1976                    scaledDataArea.getWidth(), ymax - this.zoomPoint.getY());
1977        }
1978
1979        // Draw the new zoom rectangle...
1980        if (this.useBuffer) {
1981            repaint();
1982        }
1983        else {
1984            // with no buffer, we use XOR to draw the rectangle "over" the
1985            // chart...
1986            drawZoomRectangle(g2, true);
1987        }
1988        g2.dispose();
1989
1990    }
1991
1992    /**
1993     * Handles a 'mouse released' event.  On Windows, we need to check if this
1994     * is a popup trigger, but only if we haven't already been tracking a zoom
1995     * rectangle.
1996     *
1997     * @param e  information about the event.
1998     */
1999    @Override
2000    public void mouseReleased(MouseEvent e) {
2001
2002        // if we've been panning, we need to reset now that the mouse is 
2003        // released...
2004        if (this.panLast != null) {
2005            this.panLast = null;
2006            setCursor(Cursor.getDefaultCursor());
2007        }
2008
2009        else if (this.zoomRectangle != null) {
2010            boolean hZoom, vZoom;
2011            if (this.orientation == PlotOrientation.HORIZONTAL) {
2012                hZoom = this.rangeZoomable;
2013                vZoom = this.domainZoomable;
2014            }
2015            else {
2016                hZoom = this.domainZoomable;
2017                vZoom = this.rangeZoomable;
2018            }
2019
2020            boolean zoomTrigger1 = hZoom && Math.abs(e.getX()
2021                - this.zoomPoint.getX()) >= this.zoomTriggerDistance;
2022            boolean zoomTrigger2 = vZoom && Math.abs(e.getY()
2023                - this.zoomPoint.getY()) >= this.zoomTriggerDistance;
2024            if (zoomTrigger1 || zoomTrigger2) {
2025                if ((hZoom && (e.getX() < this.zoomPoint.getX()))
2026                    || (vZoom && (e.getY() < this.zoomPoint.getY()))) {
2027                    restoreAutoBounds();
2028                }
2029                else {
2030                    double x, y, w, h;
2031                    Rectangle2D screenDataArea = getScreenDataArea(
2032                            (int) this.zoomPoint.getX(),
2033                            (int) this.zoomPoint.getY());
2034                    double maxX = screenDataArea.getMaxX();
2035                    double maxY = screenDataArea.getMaxY();
2036                    // for mouseReleased event, (horizontalZoom || verticalZoom)
2037                    // will be true, so we can just test for either being false;
2038                    // otherwise both are true
2039                    if (!vZoom) {
2040                        x = this.zoomPoint.getX();
2041                        y = screenDataArea.getMinY();
2042                        w = Math.min(this.zoomRectangle.getWidth(),
2043                                maxX - this.zoomPoint.getX());
2044                        h = screenDataArea.getHeight();
2045                    }
2046                    else if (!hZoom) {
2047                        x = screenDataArea.getMinX();
2048                        y = this.zoomPoint.getY();
2049                        w = screenDataArea.getWidth();
2050                        h = Math.min(this.zoomRectangle.getHeight(),
2051                                maxY - this.zoomPoint.getY());
2052                    }
2053                    else {
2054                        x = this.zoomPoint.getX();
2055                        y = this.zoomPoint.getY();
2056                        w = Math.min(this.zoomRectangle.getWidth(),
2057                                maxX - this.zoomPoint.getX());
2058                        h = Math.min(this.zoomRectangle.getHeight(),
2059                                maxY - this.zoomPoint.getY());
2060                    }
2061                    Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
2062                    zoom(zoomArea);
2063                }
2064                this.zoomPoint = null;
2065                this.zoomRectangle = null;
2066            }
2067            else {
2068                // erase the zoom rectangle
2069                Graphics2D g2 = (Graphics2D) getGraphics();
2070                if (this.useBuffer) {
2071                    repaint();
2072                }
2073                else {
2074                    drawZoomRectangle(g2, true);
2075                }
2076                g2.dispose();
2077                this.zoomPoint = null;
2078                this.zoomRectangle = null;
2079            }
2080
2081        }
2082
2083        else if (e.isPopupTrigger()) {
2084            if (this.popup != null) {
2085                displayPopupMenu(e.getX(), e.getY());
2086            }
2087        }
2088
2089    }
2090
2091    /**
2092     * Receives notification of mouse clicks on the panel. These are
2093     * translated and passed on to any registered {@link ChartMouseListener}s.
2094     *
2095     * @param event  Information about the mouse event.
2096     */
2097    @Override
2098    public void mouseClicked(MouseEvent event) {
2099
2100        Insets insets = getInsets();
2101        int x = (int) ((event.getX() - insets.left) / this.scaleX);
2102        int y = (int) ((event.getY() - insets.top) / this.scaleY);
2103
2104        this.anchor = new Point2D.Double(x, y);
2105        if (this.chart == null) {
2106            return;
2107        }
2108        this.chart.setNotify(true);  // force a redraw
2109        // new entity code...
2110        Object[] listeners = this.chartMouseListeners.getListeners(
2111                ChartMouseListener.class);
2112        if (listeners.length == 0) {
2113            return;
2114        }
2115
2116        ChartEntity entity = null;
2117        if (this.info != null) {
2118            EntityCollection entities = this.info.getEntityCollection();
2119            if (entities != null) {
2120                entity = entities.getEntity(x, y);
2121            }
2122        }
2123        ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event,
2124                entity);
2125        for (int i = listeners.length - 1; i >= 0; i -= 1) {
2126            ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent);
2127        }
2128
2129    }
2130
2131    /**
2132     * Implementation of the MouseMotionListener's method.
2133     *
2134     * @param e  the event.
2135     */
2136    @Override
2137    public void mouseMoved(MouseEvent e) {
2138        Graphics2D g2 = (Graphics2D) getGraphics();
2139        if (this.horizontalAxisTrace) {
2140            drawHorizontalAxisTrace(g2, e.getX());
2141        }
2142        if (this.verticalAxisTrace) {
2143            drawVerticalAxisTrace(g2, e.getY());
2144        }
2145        g2.dispose();
2146
2147        Object[] listeners = this.chartMouseListeners.getListeners(
2148                ChartMouseListener.class);
2149        if (listeners.length == 0) {
2150            return;
2151        }
2152        Insets insets = getInsets();
2153        int x = (int) ((e.getX() - insets.left) / this.scaleX);
2154        int y = (int) ((e.getY() - insets.top) / this.scaleY);
2155
2156        ChartEntity entity = null;
2157        if (this.info != null) {
2158            EntityCollection entities = this.info.getEntityCollection();
2159            if (entities != null) {
2160                entity = entities.getEntity(x, y);
2161            }
2162        }
2163
2164        // we can only generate events if the panel's chart is not null
2165        // (see bug report 1556951)
2166        if (this.chart != null) {
2167            ChartMouseEvent event = new ChartMouseEvent(getChart(), e, entity);
2168            for (int i = listeners.length - 1; i >= 0; i -= 1) {
2169                ((ChartMouseListener) listeners[i]).chartMouseMoved(event);
2170            }
2171        }
2172
2173    }
2174
2175    /**
2176     * Zooms in on an anchor point (specified in screen coordinate space).
2177     *
2178     * @param x  the x value (in screen coordinates).
2179     * @param y  the y value (in screen coordinates).
2180     */
2181    public void zoomInBoth(double x, double y) {
2182        Plot plot = this.chart.getPlot();
2183        if (plot == null) {
2184            return;
2185        }
2186        // here we tweak the notify flag on the plot so that only
2187        // one notification happens even though we update multiple
2188        // axes...
2189        boolean savedNotify = plot.isNotify();
2190        plot.setNotify(false);
2191        zoomInDomain(x, y);
2192        zoomInRange(x, y);
2193        plot.setNotify(savedNotify);
2194    }
2195
2196    /**
2197     * Decreases the length of the domain axis, centered about the given
2198     * coordinate on the screen.  The length of the domain axis is reduced
2199     * by the value of {@link #getZoomInFactor()}.
2200     *
2201     * @param x  the x coordinate (in screen coordinates).
2202     * @param y  the y-coordinate (in screen coordinates).
2203     */
2204    public void zoomInDomain(double x, double y) {
2205        Plot plot = this.chart.getPlot();
2206        if (plot instanceof Zoomable) {
2207            // here we tweak the notify flag on the plot so that only
2208            // one notification happens even though we update multiple
2209            // axes...
2210            boolean savedNotify = plot.isNotify();
2211            plot.setNotify(false);
2212            Zoomable z = (Zoomable) plot;
2213            z.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(),
2214                    translateScreenToJava2D(new Point((int) x, (int) y)),
2215                    this.zoomAroundAnchor);
2216            plot.setNotify(savedNotify);
2217        }
2218    }
2219
2220    /**
2221     * Decreases the length of the range axis, centered about the given
2222     * coordinate on the screen.  The length of the range axis is reduced by
2223     * the value of {@link #getZoomInFactor()}.
2224     *
2225     * @param x  the x-coordinate (in screen coordinates).
2226     * @param y  the y coordinate (in screen coordinates).
2227     */
2228    public void zoomInRange(double x, double y) {
2229        Plot plot = this.chart.getPlot();
2230        if (plot instanceof Zoomable) {
2231            // here we tweak the notify flag on the plot so that only
2232            // one notification happens even though we update multiple
2233            // axes...
2234            boolean savedNotify = plot.isNotify();
2235            plot.setNotify(false);
2236            Zoomable z = (Zoomable) plot;
2237            z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(),
2238                    translateScreenToJava2D(new Point((int) x, (int) y)),
2239                    this.zoomAroundAnchor);
2240            plot.setNotify(savedNotify);
2241        }
2242    }
2243
2244    /**
2245     * Zooms out on an anchor point (specified in screen coordinate space).
2246     *
2247     * @param x  the x value (in screen coordinates).
2248     * @param y  the y value (in screen coordinates).
2249     */
2250    public void zoomOutBoth(double x, double y) {
2251        Plot plot = this.chart.getPlot();
2252        if (plot == null) {
2253            return;
2254        }
2255        // here we tweak the notify flag on the plot so that only
2256        // one notification happens even though we update multiple
2257        // axes...
2258        boolean savedNotify = plot.isNotify();
2259        plot.setNotify(false);
2260        zoomOutDomain(x, y);
2261        zoomOutRange(x, y);
2262        plot.setNotify(savedNotify);
2263    }
2264
2265    /**
2266     * Increases the length of the domain axis, centered about the given
2267     * coordinate on the screen.  The length of the domain axis is increased
2268     * by the value of {@link #getZoomOutFactor()}.
2269     *
2270     * @param x  the x coordinate (in screen coordinates).
2271     * @param y  the y-coordinate (in screen coordinates).
2272     */
2273    public void zoomOutDomain(double x, double y) {
2274        Plot plot = this.chart.getPlot();
2275        if (plot instanceof Zoomable) {
2276            // here we tweak the notify flag on the plot so that only
2277            // one notification happens even though we update multiple
2278            // axes...
2279            boolean savedNotify = plot.isNotify();
2280            plot.setNotify(false);
2281            Zoomable z = (Zoomable) plot;
2282            z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(),
2283                    translateScreenToJava2D(new Point((int) x, (int) y)),
2284                    this.zoomAroundAnchor);
2285            plot.setNotify(savedNotify);
2286        }
2287    }
2288
2289    /**
2290     * Increases the length the range axis, centered about the given
2291     * coordinate on the screen.  The length of the range axis is increased
2292     * by the value of {@link #getZoomOutFactor()}.
2293     *
2294     * @param x  the x coordinate (in screen coordinates).
2295     * @param y  the y-coordinate (in screen coordinates).
2296     */
2297    public void zoomOutRange(double x, double y) {
2298        Plot plot = this.chart.getPlot();
2299        if (plot instanceof Zoomable) {
2300            // here we tweak the notify flag on the plot so that only
2301            // one notification happens even though we update multiple
2302            // axes...
2303            boolean savedNotify = plot.isNotify();
2304            plot.setNotify(false);
2305            Zoomable z = (Zoomable) plot;
2306            z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(),
2307                    translateScreenToJava2D(new Point((int) x, (int) y)),
2308                    this.zoomAroundAnchor);
2309            plot.setNotify(savedNotify);
2310        }
2311    }
2312
2313    /**
2314     * Zooms in on a selected region.
2315     *
2316     * @param selection  the selected region.
2317     */
2318    public void zoom(Rectangle2D selection) {
2319
2320        // get the origin of the zoom selection in the Java2D space used for
2321        // drawing the chart (that is, before any scaling to fit the panel)
2322        Point2D selectOrigin = translateScreenToJava2D(new Point(
2323                (int) Math.ceil(selection.getX()),
2324                (int) Math.ceil(selection.getY())));
2325        PlotRenderingInfo plotInfo = this.info.getPlotInfo();
2326        Rectangle2D scaledDataArea = getScreenDataArea(
2327                (int) selection.getCenterX(), (int) selection.getCenterY());
2328        if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) {
2329
2330            double hLower = (selection.getMinX() - scaledDataArea.getMinX())
2331                / scaledDataArea.getWidth();
2332            double hUpper = (selection.getMaxX() - scaledDataArea.getMinX())
2333                / scaledDataArea.getWidth();
2334            double vLower = (scaledDataArea.getMaxY() - selection.getMaxY())
2335                / scaledDataArea.getHeight();
2336            double vUpper = (scaledDataArea.getMaxY() - selection.getMinY())
2337                / scaledDataArea.getHeight();
2338
2339            Plot p = this.chart.getPlot();
2340            if (p instanceof Zoomable) {
2341                // here we tweak the notify flag on the plot so that only
2342                // one notification happens even though we update multiple
2343                // axes...
2344                boolean savedNotify = p.isNotify();
2345                p.setNotify(false);
2346                Zoomable z = (Zoomable) p;
2347                if (z.getOrientation() == PlotOrientation.HORIZONTAL) {
2348                    z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin);
2349                    z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin);
2350                }
2351                else {
2352                    z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin);
2353                    z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin);
2354                }
2355                p.setNotify(savedNotify);
2356            }
2357
2358        }
2359
2360    }
2361
2362    /**
2363     * Restores the auto-range calculation on both axes.
2364     */
2365    public void restoreAutoBounds() {
2366        Plot plot = this.chart.getPlot();
2367        if (plot == null) {
2368            return;
2369        }
2370        // here we tweak the notify flag on the plot so that only
2371        // one notification happens even though we update multiple
2372        // axes...
2373        boolean savedNotify = plot.isNotify();
2374        plot.setNotify(false);
2375        restoreAutoDomainBounds();
2376        restoreAutoRangeBounds();
2377        plot.setNotify(savedNotify);
2378    }
2379
2380    /**
2381     * Restores the auto-range calculation on the domain axis.
2382     */
2383    public void restoreAutoDomainBounds() {
2384        Plot plot = this.chart.getPlot();
2385        if (plot instanceof Zoomable) {
2386            Zoomable z = (Zoomable) plot;
2387            // here we tweak the notify flag on the plot so that only
2388            // one notification happens even though we update multiple
2389            // axes...
2390            boolean savedNotify = plot.isNotify();
2391            plot.setNotify(false);
2392            // we need to guard against this.zoomPoint being null
2393            Point2D zp = (this.zoomPoint != null
2394                    ? this.zoomPoint : new Point());
2395            z.zoomDomainAxes(0.0, this.info.getPlotInfo(), zp);
2396            plot.setNotify(savedNotify);
2397        }
2398    }
2399
2400    /**
2401     * Restores the auto-range calculation on the range axis.
2402     */
2403    public void restoreAutoRangeBounds() {
2404        Plot plot = this.chart.getPlot();
2405        if (plot instanceof Zoomable) {
2406            Zoomable z = (Zoomable) plot;
2407            // here we tweak the notify flag on the plot so that only
2408            // one notification happens even though we update multiple
2409            // axes...
2410            boolean savedNotify = plot.isNotify();
2411            plot.setNotify(false);
2412            // we need to guard against this.zoomPoint being null
2413            Point2D zp = (this.zoomPoint != null
2414                    ? this.zoomPoint : new Point());
2415            z.zoomRangeAxes(0.0, this.info.getPlotInfo(), zp);
2416            plot.setNotify(savedNotify);
2417        }
2418    }
2419
2420    /**
2421     * Returns the data area for the chart (the area inside the axes) with the
2422     * current scaling applied (that is, the area as it appears on screen).
2423     *
2424     * @return The scaled data area.
2425     */
2426    public Rectangle2D getScreenDataArea() {
2427        Rectangle2D dataArea = this.info.getPlotInfo().getDataArea();
2428        Insets insets = getInsets();
2429        double x = dataArea.getX() * this.scaleX + insets.left;
2430        double y = dataArea.getY() * this.scaleY + insets.top;
2431        double w = dataArea.getWidth() * this.scaleX;
2432        double h = dataArea.getHeight() * this.scaleY;
2433        return new Rectangle2D.Double(x, y, w, h);
2434    }
2435
2436    /**
2437     * Returns the data area (the area inside the axes) for the plot or subplot,
2438     * with the current scaling applied.
2439     *
2440     * @param x  the x-coordinate (for subplot selection).
2441     * @param y  the y-coordinate (for subplot selection).
2442     *
2443     * @return The scaled data area.
2444     */
2445    public Rectangle2D getScreenDataArea(int x, int y) {
2446        PlotRenderingInfo plotInfo = this.info.getPlotInfo();
2447        Rectangle2D result;
2448        if (plotInfo.getSubplotCount() == 0) {
2449            result = getScreenDataArea();
2450        }
2451        else {
2452            // get the origin of the zoom selection in the Java2D space used for
2453            // drawing the chart (that is, before any scaling to fit the panel)
2454            Point2D selectOrigin = translateScreenToJava2D(new Point(x, y));
2455            int subplotIndex = plotInfo.getSubplotIndex(selectOrigin);
2456            if (subplotIndex == -1) {
2457                return null;
2458            }
2459            result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea());
2460        }
2461        return result;
2462    }
2463
2464    /**
2465     * Returns the initial tooltip delay value used inside this chart panel.
2466     *
2467     * @return An integer representing the initial delay value, in milliseconds.
2468     *
2469     * @see javax.swing.ToolTipManager#getInitialDelay()
2470     */
2471    public int getInitialDelay() {
2472        return this.ownToolTipInitialDelay;
2473    }
2474
2475    /**
2476     * Returns the reshow tooltip delay value used inside this chart panel.
2477     *
2478     * @return An integer representing the reshow  delay value, in milliseconds.
2479     *
2480     * @see javax.swing.ToolTipManager#getReshowDelay()
2481     */
2482    public int getReshowDelay() {
2483        return this.ownToolTipReshowDelay;
2484    }
2485
2486    /**
2487     * Returns the dismissal tooltip delay value used inside this chart panel.
2488     *
2489     * @return An integer representing the dismissal delay value, in
2490     *         milliseconds.
2491     *
2492     * @see javax.swing.ToolTipManager#getDismissDelay()
2493     */
2494    public int getDismissDelay() {
2495        return this.ownToolTipDismissDelay;
2496    }
2497
2498    /**
2499     * Specifies the initial delay value for this chart panel.
2500     *
2501     * @param delay  the number of milliseconds to delay (after the cursor has
2502     *               paused) before displaying.
2503     *
2504     * @see javax.swing.ToolTipManager#setInitialDelay(int)
2505     */
2506    public void setInitialDelay(int delay) {
2507        this.ownToolTipInitialDelay = delay;
2508    }
2509
2510    /**
2511     * Specifies the amount of time before the user has to wait initialDelay
2512     * milliseconds before a tooltip will be shown.
2513     *
2514     * @param delay  time in milliseconds
2515     *
2516     * @see javax.swing.ToolTipManager#setReshowDelay(int)
2517     */
2518    public void setReshowDelay(int delay) {
2519        this.ownToolTipReshowDelay = delay;
2520    }
2521
2522    /**
2523     * Specifies the dismissal delay value for this chart panel.
2524     *
2525     * @param delay the number of milliseconds to delay before taking away the
2526     *              tooltip
2527     *
2528     * @see javax.swing.ToolTipManager#setDismissDelay(int)
2529     */
2530    public void setDismissDelay(int delay) {
2531        this.ownToolTipDismissDelay = delay;
2532    }
2533
2534    /**
2535     * Returns the zoom in factor.
2536     *
2537     * @return The zoom in factor.
2538     *
2539     * @see #setZoomInFactor(double)
2540     */
2541    public double getZoomInFactor() {
2542        return this.zoomInFactor;
2543    }
2544
2545    /**
2546     * Sets the zoom in factor.
2547     *
2548     * @param factor  the factor.
2549     *
2550     * @see #getZoomInFactor()
2551     */
2552    public void setZoomInFactor(double factor) {
2553        this.zoomInFactor = factor;
2554    }
2555
2556    /**
2557     * Returns the zoom out factor.
2558     *
2559     * @return The zoom out factor.
2560     *
2561     * @see #setZoomOutFactor(double)
2562     */
2563    public double getZoomOutFactor() {
2564        return this.zoomOutFactor;
2565    }
2566
2567    /**
2568     * Sets the zoom out factor.
2569     *
2570     * @param factor  the factor.
2571     *
2572     * @see #getZoomOutFactor()
2573     */
2574    public void setZoomOutFactor(double factor) {
2575        this.zoomOutFactor = factor;
2576    }
2577
2578    /**
2579     * Draws zoom rectangle (if present).
2580     * The drawing is performed in XOR mode, therefore
2581     * when this method is called twice in a row,
2582     * the second call will completely restore the state
2583     * of the canvas.
2584     *
2585     * @param g2 the graphics device.
2586     * @param xor  use XOR for drawing?
2587     */
2588    private void drawZoomRectangle(Graphics2D g2, boolean xor) {
2589        if (this.zoomRectangle != null) {
2590            if (xor) {
2591                 // Set XOR mode to draw the zoom rectangle
2592                g2.setXORMode(Color.gray);
2593            }
2594            if (this.fillZoomRectangle) {
2595                g2.setPaint(this.zoomFillPaint);
2596                g2.fill(this.zoomRectangle);
2597            }
2598            else {
2599                g2.setPaint(this.zoomOutlinePaint);
2600                g2.draw(this.zoomRectangle);
2601            }
2602            if (xor) {
2603                // Reset to the default 'overwrite' mode
2604                g2.setPaintMode();
2605            }
2606        }
2607    }
2608
2609    /**
2610     * Draws a vertical line used to trace the mouse position to the horizontal
2611     * axis.
2612     *
2613     * @param g2 the graphics device.
2614     * @param x  the x-coordinate of the trace line.
2615     */
2616    private void drawHorizontalAxisTrace(Graphics2D g2, int x) {
2617
2618        Rectangle2D dataArea = getScreenDataArea();
2619
2620        g2.setXORMode(Color.orange);
2621        if (((int) dataArea.getMinX() < x) && (x < (int) dataArea.getMaxX())) {
2622
2623            if (this.verticalTraceLine != null) {
2624                g2.draw(this.verticalTraceLine);
2625                this.verticalTraceLine.setLine(x, (int) dataArea.getMinY(), x,
2626                        (int) dataArea.getMaxY());
2627            }
2628            else {
2629                this.verticalTraceLine = new Line2D.Float(x,
2630                        (int) dataArea.getMinY(), x, (int) dataArea.getMaxY());
2631            }
2632            g2.draw(this.verticalTraceLine);
2633        }
2634
2635        // Reset to the default 'overwrite' mode
2636        g2.setPaintMode();
2637    }
2638
2639    /**
2640     * Draws a horizontal line used to trace the mouse position to the vertical
2641     * axis.
2642     *
2643     * @param g2 the graphics device.
2644     * @param y  the y-coordinate of the trace line.
2645     */
2646    private void drawVerticalAxisTrace(Graphics2D g2, int y) {
2647
2648        Rectangle2D dataArea = getScreenDataArea();
2649
2650        g2.setXORMode(Color.orange);
2651        if (((int) dataArea.getMinY() < y) && (y < (int) dataArea.getMaxY())) {
2652
2653            if (this.horizontalTraceLine != null) {
2654                g2.draw(this.horizontalTraceLine);
2655                this.horizontalTraceLine.setLine((int) dataArea.getMinX(), y,
2656                        (int) dataArea.getMaxX(), y);
2657            }
2658            else {
2659                this.horizontalTraceLine = new Line2D.Float(
2660                        (int) dataArea.getMinX(), y, (int) dataArea.getMaxX(),
2661                        y);
2662            }
2663            g2.draw(this.horizontalTraceLine);
2664        }
2665
2666        // Reset to the default 'overwrite' mode
2667        g2.setPaintMode();
2668    }
2669
2670    /**
2671     * Displays a dialog that allows the user to edit the properties for the
2672     * current chart.
2673     *
2674     * @since 1.0.3
2675     */
2676    public void doEditChartProperties() {
2677
2678        ChartEditor editor = ChartEditorManager.getChartEditor(this.chart);
2679        int result = JOptionPane.showConfirmDialog(this, editor,
2680                localizationResources.getString("Chart_Properties"),
2681                JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
2682        if (result == JOptionPane.OK_OPTION) {
2683            editor.updateChart(this.chart);
2684        }
2685
2686    }
2687
2688    /**
2689     * Copies the current chart to the system clipboard.
2690     * 
2691     * @since 1.0.13
2692     */
2693    public void doCopy() {
2694        Clipboard systemClipboard
2695                = Toolkit.getDefaultToolkit().getSystemClipboard();
2696        Insets insets = getInsets();
2697        int w = getWidth() - insets.left - insets.right;
2698        int h = getHeight() - insets.top - insets.bottom;
2699        ChartTransferable selection = new ChartTransferable(this.chart, w, h,
2700                getMinimumDrawWidth(), getMinimumDrawHeight(),
2701                getMaximumDrawWidth(), getMaximumDrawHeight(), true);
2702        systemClipboard.setContents(selection, null);
2703    }
2704
2705    /**
2706     * Opens a file chooser and gives the user an opportunity to save the chart
2707     * in PNG format.
2708     *
2709     * @throws IOException if there is an I/O error.
2710     */
2711    public void doSaveAs() throws IOException {
2712        JFileChooser fileChooser = new JFileChooser();
2713        fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
2714        FileNameExtensionFilter filter = new FileNameExtensionFilter(
2715                    localizationResources.getString("PNG_Image_Files"), "png");
2716        fileChooser.addChoosableFileFilter(filter);
2717        fileChooser.setFileFilter(filter);
2718
2719        int option = fileChooser.showSaveDialog(this);
2720        if (option == JFileChooser.APPROVE_OPTION) {
2721            String filename = fileChooser.getSelectedFile().getPath();
2722            if (isEnforceFileExtensions()) {
2723                if (!filename.endsWith(".png")) {
2724                    filename = filename + ".png";
2725                }
2726            }
2727            ChartUtilities.saveChartAsPNG(new File(filename), this.chart,
2728                    getWidth(), getHeight());
2729        }
2730    }
2731    
2732    /**
2733     * Saves the chart in SVG format (a filechooser will be displayed so that
2734     * the user can specify the filename).  Note that this method only works
2735     * if the JFreeSVG library is on the classpath...if this library is not 
2736     * present, the method will fail.
2737     */
2738    private void saveAsSVG(File f) throws IOException {
2739        File file = f;
2740        if (file == null) {
2741            JFileChooser fileChooser = new JFileChooser();
2742            fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
2743            FileNameExtensionFilter filter = new FileNameExtensionFilter(
2744                    localizationResources.getString("SVG_Files"), "svg");
2745            fileChooser.addChoosableFileFilter(filter);
2746            fileChooser.setFileFilter(filter);
2747
2748            int option = fileChooser.showSaveDialog(this);
2749            if (option == JFileChooser.APPROVE_OPTION) {
2750                String filename = fileChooser.getSelectedFile().getPath();
2751                if (isEnforceFileExtensions()) {
2752                    if (!filename.endsWith(".svg")) {
2753                        filename = filename + ".svg";
2754                    }
2755                }
2756                file = new File(filename);
2757                if (file.exists()) {
2758                    String fileExists = localizationResources.getString(
2759                            "FILE_EXISTS_CONFIRM_OVERWRITE");
2760                    int response = JOptionPane.showConfirmDialog(this, 
2761                            fileExists, "Save As SVG", 
2762                            JOptionPane.OK_CANCEL_OPTION);
2763                    if (response == JOptionPane.CANCEL_OPTION) {
2764                        file = null;
2765                    }
2766                }
2767            }
2768        }
2769        
2770        if (file != null) {
2771            // use reflection to get the SVG string
2772            String svg = generateSVG(getWidth(), getHeight());
2773            BufferedWriter writer = null;
2774            try {
2775                writer = new BufferedWriter(new FileWriter(file));
2776                writer.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
2777                writer.write(svg + "\n");
2778                writer.flush();
2779            } finally {
2780                try {
2781                    if (writer != null) {
2782                        writer.close();
2783                    }
2784                } catch (IOException ex) {
2785                    throw new RuntimeException(ex);
2786                }
2787            } 
2788
2789        }
2790    }
2791    
2792    /**
2793     * Generates a string containing a rendering of the chart in SVG format.
2794     * This feature is only supported if the JFreeSVG library is included on 
2795     * the classpath.
2796     * 
2797     * @return A string containing an SVG element for the current chart, or 
2798     *     <code>null</code> if there is a problem with the method invocation
2799     *     by reflection.
2800     */
2801    private String generateSVG(int width, int height) {
2802        Graphics2D g2 = createSVGGraphics2D(width, height);
2803        if (g2 == null) {
2804            throw new IllegalStateException("JFreeSVG library is not present.");
2805        }
2806        // we suppress shadow generation, because SVG is a vector format and
2807        // the shadow effect is applied via bitmap effects...
2808        g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true);
2809        String svg = null;
2810        Rectangle2D drawArea = new Rectangle2D.Double(0, 0, width, height);
2811        this.chart.draw(g2, drawArea);
2812        try {
2813            Method m = g2.getClass().getMethod("getSVGElement");
2814            svg = (String) m.invoke(g2);
2815        } catch (NoSuchMethodException e) {
2816            // null will be returned
2817        } catch (SecurityException e) {
2818            // null will be returned
2819        } catch (IllegalAccessException e) {
2820            // null will be returned
2821        } catch (IllegalArgumentException e) {
2822            // null will be returned
2823        } catch (InvocationTargetException e) {
2824            // null will be returned
2825        }
2826        return svg;
2827    }
2828
2829    private Graphics2D createSVGGraphics2D(int w, int h) {
2830        try {
2831            Class svgGraphics2d = Class.forName("org.jfree.graphics2d.svg.SVGGraphics2D");
2832            Constructor ctor = svgGraphics2d.getConstructor(int.class, int.class);
2833            return (Graphics2D) ctor.newInstance(w, h);
2834        } catch (ClassNotFoundException ex) {
2835            return null;
2836        } catch (NoSuchMethodException ex) {
2837            return null;
2838        } catch (SecurityException ex) {
2839            return null;
2840        } catch (InstantiationException ex) {
2841            return null;
2842        } catch (IllegalAccessException ex) {
2843            return null;
2844        } catch (IllegalArgumentException ex) {
2845            return null;
2846        } catch (InvocationTargetException ex) {
2847            return null;
2848        }
2849    }
2850
2851    /**
2852     * Saves the chart in PDF format (a filechooser will be displayed so that
2853     * the user can specify the filename).  Note that this method only works
2854     * if the OrsonPDF library is on the classpath...if this library is not
2855     * present, the method will fail.
2856     */
2857    private void saveAsPDF(File f) {
2858        File file = f;
2859        if (file == null) {
2860            JFileChooser fileChooser = new JFileChooser();
2861            fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
2862            FileNameExtensionFilter filter = new FileNameExtensionFilter(
2863                    localizationResources.getString("PDF_Files"), "pdf");
2864            fileChooser.addChoosableFileFilter(filter);
2865            fileChooser.setFileFilter(filter);
2866
2867            int option = fileChooser.showSaveDialog(this);
2868            if (option == JFileChooser.APPROVE_OPTION) {
2869                String filename = fileChooser.getSelectedFile().getPath();
2870                if (isEnforceFileExtensions()) {
2871                    if (!filename.endsWith(".pdf")) {
2872                        filename = filename + ".pdf";
2873                    }
2874                }
2875                file = new File(filename);
2876                if (file.exists()) {
2877                    String fileExists = localizationResources.getString(
2878                            "FILE_EXISTS_CONFIRM_OVERWRITE");
2879                    int response = JOptionPane.showConfirmDialog(this, 
2880                            fileExists, "Save As PDF", 
2881                            JOptionPane.OK_CANCEL_OPTION);
2882                    if (response == JOptionPane.CANCEL_OPTION) {
2883                        file = null;
2884                    }
2885                }
2886            }
2887        }
2888        
2889        if (file != null) {
2890            writeAsPDF(file, getWidth(), getHeight());
2891        }
2892    }
2893
2894    /**
2895     * Returns <code>true</code> if OrsonPDF is on the classpath, and 
2896     * <code>false</code> otherwise.  The OrsonPDF library can be found at
2897     * http://www.object-refinery.com/pdf/
2898     * 
2899     * @return A boolean.
2900     */
2901    private boolean isOrsonPDFAvailable() {
2902        Class pdfDocumentClass = null;
2903        try {
2904            pdfDocumentClass = Class.forName("com.orsonpdf.PDFDocument");
2905        } catch (ClassNotFoundException e) {
2906            // pdfDocument class will be null so the function will return false
2907        }
2908        return (pdfDocumentClass != null);
2909    }
2910    
2911    /**
2912     * Writes the current chart to the specified file in PDF format.  This 
2913     * will only work when the OrsonPDF library is found on the classpath.
2914     * Reflection is used to ensure there is no compile-time dependency on
2915     * OrsonPDF (which is non-free software).
2916     * 
2917     * @param file  the output file (<code>null</code> not permitted).
2918     * @param w  the chart width.
2919     * @param h  the chart height.
2920     */
2921    private void writeAsPDF(File file, int w, int h) {
2922        if (!isOrsonPDFAvailable()) {
2923            throw new IllegalStateException(
2924                    "OrsonPDF is not present on the classpath.");
2925        }
2926        ParamChecks.nullNotPermitted(file, "file");
2927        try {
2928            Class pdfDocClass = Class.forName("com.orsonpdf.PDFDocument");
2929            Object pdfDoc = pdfDocClass.newInstance();
2930            Method m = pdfDocClass.getMethod("createPage", Rectangle2D.class);
2931            Rectangle2D rect = new Rectangle(w, h);
2932            Object page = m.invoke(pdfDoc, rect);
2933            Method m2 = page.getClass().getMethod("getGraphics2D");
2934            Graphics2D g2 = (Graphics2D) m2.invoke(page);
2935            // we suppress shadow generation, because PDF is a vector format and
2936            // the shadow effect is applied via bitmap effects...
2937            g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true);
2938            Rectangle2D drawArea = new Rectangle2D.Double(0, 0, w, h);
2939            this.chart.draw(g2, drawArea);
2940            Method m3 = pdfDocClass.getMethod("writeToFile", File.class);
2941            m3.invoke(pdfDoc, file);
2942        } catch (ClassNotFoundException ex) {
2943            throw new RuntimeException(ex);
2944        } catch (InstantiationException ex) {
2945            throw new RuntimeException(ex);
2946        } catch (IllegalAccessException ex) {
2947            throw new RuntimeException(ex);
2948        } catch (NoSuchMethodException ex) {
2949            throw new RuntimeException(ex);
2950        } catch (SecurityException ex) {
2951            throw new RuntimeException(ex);
2952        } catch (IllegalArgumentException ex) {
2953            throw new RuntimeException(ex);
2954        } catch (InvocationTargetException ex) {
2955            throw new RuntimeException(ex);
2956        }
2957    }
2958
2959    /**
2960     * Creates a print job for the chart.
2961     */
2962    public void createChartPrintJob() {
2963        PrinterJob job = PrinterJob.getPrinterJob();
2964        PageFormat pf = job.defaultPage();
2965        PageFormat pf2 = job.pageDialog(pf);
2966        if (pf2 != pf) {
2967            job.setPrintable(this, pf2);
2968            if (job.printDialog()) {
2969                try {
2970                    job.print();
2971                }
2972                catch (PrinterException e) {
2973                    JOptionPane.showMessageDialog(this, e);
2974                }
2975            }
2976        }
2977    }
2978
2979    /**
2980     * Prints the chart on a single page.
2981     *
2982     * @param g  the graphics context.
2983     * @param pf  the page format to use.
2984     * @param pageIndex  the index of the page. If not <code>0</code>, nothing
2985     *                   gets print.
2986     *
2987     * @return The result of printing.
2988     */
2989    @Override
2990    public int print(Graphics g, PageFormat pf, int pageIndex) {
2991
2992        if (pageIndex != 0) {
2993            return NO_SUCH_PAGE;
2994        }
2995        Graphics2D g2 = (Graphics2D) g;
2996        double x = pf.getImageableX();
2997        double y = pf.getImageableY();
2998        double w = pf.getImageableWidth();
2999        double h = pf.getImageableHeight();
3000        this.chart.draw(g2, new Rectangle2D.Double(x, y, w, h), this.anchor,
3001                null);
3002        return PAGE_EXISTS;
3003
3004    }
3005
3006    /**
3007     * Adds a listener to the list of objects listening for chart mouse events.
3008     *
3009     * @param listener  the listener (<code>null</code> not permitted).
3010     */
3011    public void addChartMouseListener(ChartMouseListener listener) {
3012        ParamChecks.nullNotPermitted(listener, "listener");
3013        this.chartMouseListeners.add(ChartMouseListener.class, listener);
3014    }
3015
3016    /**
3017     * Removes a listener from the list of objects listening for chart mouse
3018     * events.
3019     *
3020     * @param listener  the listener.
3021     */
3022    public void removeChartMouseListener(ChartMouseListener listener) {
3023        this.chartMouseListeners.remove(ChartMouseListener.class, listener);
3024    }
3025
3026    /**
3027     * Returns an array of the listeners of the given type registered with the
3028     * panel.
3029     *
3030     * @param listenerType  the listener type.
3031     *
3032     * @return An array of listeners.
3033     */
3034    @Override
3035    public EventListener[] getListeners(Class listenerType) {
3036        if (listenerType == ChartMouseListener.class) {
3037            // fetch listeners from local storage
3038            return this.chartMouseListeners.getListeners(listenerType);
3039        }
3040        else {
3041            return super.getListeners(listenerType);
3042        }
3043    }
3044
3045    /**
3046     * Creates a popup menu for the panel.
3047     *
3048     * @param properties  include a menu item for the chart property editor.
3049     * @param save  include a menu item for saving the chart.
3050     * @param print  include a menu item for printing the chart.
3051     * @param zoom  include menu items for zooming.
3052     *
3053     * @return The popup menu.
3054     */
3055    protected JPopupMenu createPopupMenu(boolean properties, boolean save,
3056            boolean print, boolean zoom) {
3057        return createPopupMenu(properties, false, save, print, zoom);
3058    }
3059
3060    /**
3061     * Creates a popup menu for the panel.
3062     *
3063     * @param properties  include a menu item for the chart property editor.
3064     * @param copy include a menu item for copying to the clipboard.
3065     * @param save  include a menu item for saving the chart.
3066     * @param print  include a menu item for printing the chart.
3067     * @param zoom  include menu items for zooming.
3068     *
3069     * @return The popup menu.
3070     *
3071     * @since 1.0.13
3072     */
3073    protected JPopupMenu createPopupMenu(boolean properties,
3074            boolean copy, boolean save, boolean print, boolean zoom) {
3075
3076        JPopupMenu result = new JPopupMenu(localizationResources.getString("Chart") + ":");
3077        boolean separator = false;
3078
3079        if (properties) {
3080            JMenuItem propertiesItem = new JMenuItem(
3081                    localizationResources.getString("Properties..."));
3082            propertiesItem.setActionCommand(PROPERTIES_COMMAND);
3083            propertiesItem.addActionListener(this);
3084            result.add(propertiesItem);
3085            separator = true;
3086        }
3087
3088        if (copy) {
3089            if (separator) {
3090                result.addSeparator();
3091            }
3092            JMenuItem copyItem = new JMenuItem(
3093                    localizationResources.getString("Copy"));
3094            copyItem.setActionCommand(COPY_COMMAND);
3095            copyItem.addActionListener(this);
3096            result.add(copyItem);
3097            separator = !save;
3098        }
3099
3100        if (save) {
3101            if (separator) {
3102                result.addSeparator();
3103            }
3104            JMenu saveSubMenu = new JMenu("Save as");
3105            JMenuItem pngItem = new JMenuItem("PNG...");
3106            pngItem.setActionCommand("SAVE_AS_PNG");
3107            pngItem.addActionListener(this);
3108            saveSubMenu.add(pngItem);
3109            
3110            if (createSVGGraphics2D(10, 10) != null) {
3111                JMenuItem svgItem = new JMenuItem("SVG...");
3112                svgItem.setActionCommand("SAVE_AS_SVG");
3113                svgItem.addActionListener(this);
3114                saveSubMenu.add(svgItem);                
3115            }
3116            
3117            if (isOrsonPDFAvailable()) {
3118                JMenuItem pdfItem = new JMenuItem("PDF...");
3119                pdfItem.setActionCommand("SAVE_AS_PDF");
3120                pdfItem.addActionListener(this);
3121                saveSubMenu.add(pdfItem);
3122            }
3123            result.add(saveSubMenu);
3124            separator = true;
3125        }
3126
3127        if (print) {
3128            if (separator) {
3129                result.addSeparator();
3130            }
3131            JMenuItem printItem = new JMenuItem(
3132                    localizationResources.getString("Print..."));
3133            printItem.setActionCommand(PRINT_COMMAND);
3134            printItem.addActionListener(this);
3135            result.add(printItem);
3136            separator = true;
3137        }
3138
3139        if (zoom) {
3140            if (separator) {
3141                result.addSeparator();
3142            }
3143
3144            JMenu zoomInMenu = new JMenu(
3145                    localizationResources.getString("Zoom_In"));
3146
3147            this.zoomInBothMenuItem = new JMenuItem(
3148                    localizationResources.getString("All_Axes"));
3149            this.zoomInBothMenuItem.setActionCommand(ZOOM_IN_BOTH_COMMAND);
3150            this.zoomInBothMenuItem.addActionListener(this);
3151            zoomInMenu.add(this.zoomInBothMenuItem);
3152
3153            zoomInMenu.addSeparator();
3154
3155            this.zoomInDomainMenuItem = new JMenuItem(
3156                    localizationResources.getString("Domain_Axis"));
3157            this.zoomInDomainMenuItem.setActionCommand(ZOOM_IN_DOMAIN_COMMAND);
3158            this.zoomInDomainMenuItem.addActionListener(this);
3159            zoomInMenu.add(this.zoomInDomainMenuItem);
3160
3161            this.zoomInRangeMenuItem = new JMenuItem(
3162                    localizationResources.getString("Range_Axis"));
3163            this.zoomInRangeMenuItem.setActionCommand(ZOOM_IN_RANGE_COMMAND);
3164            this.zoomInRangeMenuItem.addActionListener(this);
3165            zoomInMenu.add(this.zoomInRangeMenuItem);
3166
3167            result.add(zoomInMenu);
3168
3169            JMenu zoomOutMenu = new JMenu(
3170                    localizationResources.getString("Zoom_Out"));
3171
3172            this.zoomOutBothMenuItem = new JMenuItem(
3173                    localizationResources.getString("All_Axes"));
3174            this.zoomOutBothMenuItem.setActionCommand(ZOOM_OUT_BOTH_COMMAND);
3175            this.zoomOutBothMenuItem.addActionListener(this);
3176            zoomOutMenu.add(this.zoomOutBothMenuItem);
3177
3178            zoomOutMenu.addSeparator();
3179
3180            this.zoomOutDomainMenuItem = new JMenuItem(
3181                    localizationResources.getString("Domain_Axis"));
3182            this.zoomOutDomainMenuItem.setActionCommand(
3183                    ZOOM_OUT_DOMAIN_COMMAND);
3184            this.zoomOutDomainMenuItem.addActionListener(this);
3185            zoomOutMenu.add(this.zoomOutDomainMenuItem);
3186
3187            this.zoomOutRangeMenuItem = new JMenuItem(
3188                    localizationResources.getString("Range_Axis"));
3189            this.zoomOutRangeMenuItem.setActionCommand(ZOOM_OUT_RANGE_COMMAND);
3190            this.zoomOutRangeMenuItem.addActionListener(this);
3191            zoomOutMenu.add(this.zoomOutRangeMenuItem);
3192
3193            result.add(zoomOutMenu);
3194
3195            JMenu autoRangeMenu = new JMenu(
3196                    localizationResources.getString("Auto_Range"));
3197
3198            this.zoomResetBothMenuItem = new JMenuItem(
3199                    localizationResources.getString("All_Axes"));
3200            this.zoomResetBothMenuItem.setActionCommand(
3201                    ZOOM_RESET_BOTH_COMMAND);
3202            this.zoomResetBothMenuItem.addActionListener(this);
3203            autoRangeMenu.add(this.zoomResetBothMenuItem);
3204
3205            autoRangeMenu.addSeparator();
3206            this.zoomResetDomainMenuItem = new JMenuItem(
3207                    localizationResources.getString("Domain_Axis"));
3208            this.zoomResetDomainMenuItem.setActionCommand(
3209                    ZOOM_RESET_DOMAIN_COMMAND);
3210            this.zoomResetDomainMenuItem.addActionListener(this);
3211            autoRangeMenu.add(this.zoomResetDomainMenuItem);
3212
3213            this.zoomResetRangeMenuItem = new JMenuItem(
3214                    localizationResources.getString("Range_Axis"));
3215            this.zoomResetRangeMenuItem.setActionCommand(
3216                    ZOOM_RESET_RANGE_COMMAND);
3217            this.zoomResetRangeMenuItem.addActionListener(this);
3218            autoRangeMenu.add(this.zoomResetRangeMenuItem);
3219
3220            result.addSeparator();
3221            result.add(autoRangeMenu);
3222
3223        }
3224
3225        return result;
3226
3227    }
3228
3229    /**
3230     * The idea is to modify the zooming options depending on the type of chart
3231     * being displayed by the panel.
3232     *
3233     * @param x  horizontal position of the popup.
3234     * @param y  vertical position of the popup.
3235     */
3236    protected void displayPopupMenu(int x, int y) {
3237
3238        if (this.popup == null) {
3239            return;
3240        }
3241
3242        // go through each zoom menu item and decide whether or not to
3243        // enable it...
3244        boolean isDomainZoomable = false;
3245        boolean isRangeZoomable = false;
3246        Plot plot = (this.chart != null ? this.chart.getPlot() : null);
3247        if (plot instanceof Zoomable) {
3248            Zoomable z = (Zoomable) plot;
3249            isDomainZoomable = z.isDomainZoomable();
3250            isRangeZoomable = z.isRangeZoomable();
3251        }
3252
3253        if (this.zoomInDomainMenuItem != null) {
3254            this.zoomInDomainMenuItem.setEnabled(isDomainZoomable);
3255        }
3256        if (this.zoomOutDomainMenuItem != null) {
3257            this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable);
3258        }
3259        if (this.zoomResetDomainMenuItem != null) {
3260            this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable);
3261        }
3262
3263        if (this.zoomInRangeMenuItem != null) {
3264            this.zoomInRangeMenuItem.setEnabled(isRangeZoomable);
3265        }
3266        if (this.zoomOutRangeMenuItem != null) {
3267            this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable);
3268        }
3269
3270        if (this.zoomResetRangeMenuItem != null) {
3271            this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable);
3272        }
3273
3274        if (this.zoomInBothMenuItem != null) {
3275            this.zoomInBothMenuItem.setEnabled(isDomainZoomable
3276                    && isRangeZoomable);
3277        }
3278        if (this.zoomOutBothMenuItem != null) {
3279            this.zoomOutBothMenuItem.setEnabled(isDomainZoomable
3280                    && isRangeZoomable);
3281        }
3282        if (this.zoomResetBothMenuItem != null) {
3283            this.zoomResetBothMenuItem.setEnabled(isDomainZoomable
3284                    && isRangeZoomable);
3285        }
3286
3287        this.popup.show(this, x, y);
3288
3289    }
3290
3291    /**
3292     * Updates the UI for a LookAndFeel change.
3293     */
3294    @Override
3295    public void updateUI() {
3296        // here we need to update the UI for the popup menu, if the panel
3297        // has one...
3298        if (this.popup != null) {
3299            SwingUtilities.updateComponentTreeUI(this.popup);
3300        }
3301        super.updateUI();
3302    }
3303
3304    /**
3305     * Provides serialization support.
3306     *
3307     * @param stream  the output stream.
3308     *
3309     * @throws IOException  if there is an I/O error.
3310     */
3311    private void writeObject(ObjectOutputStream stream) throws IOException {
3312        stream.defaultWriteObject();
3313        SerialUtilities.writePaint(this.zoomFillPaint, stream);
3314        SerialUtilities.writePaint(this.zoomOutlinePaint, stream);
3315    }
3316
3317    /**
3318     * Provides serialization support.
3319     *
3320     * @param stream  the input stream.
3321     *
3322     * @throws IOException  if there is an I/O error.
3323     * @throws ClassNotFoundException  if there is a classpath problem.
3324     */
3325    private void readObject(ObjectInputStream stream)
3326        throws IOException, ClassNotFoundException {
3327        stream.defaultReadObject();
3328        this.zoomFillPaint = SerialUtilities.readPaint(stream);
3329        this.zoomOutlinePaint = SerialUtilities.readPaint(stream);
3330
3331        // we create a new but empty chartMouseListeners list
3332        this.chartMouseListeners = new EventListenerList();
3333
3334        // register as a listener with sub-components...
3335        if (this.chart != null) {
3336            this.chart.addChangeListener(this);
3337        }
3338
3339    }
3340
3341}