001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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-2014, 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 enabled 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                } else {
1628                    this.chart.draw(bufferG2, bufferArea, this.anchor,
1629                            this.info);
1630                }
1631
1632            }
1633
1634            // zap the buffer onto the panel...
1635            g2.drawImage(this.chartBuffer, insets.left, insets.top, this);
1636
1637        } else { // redrawing the chart every time...
1638            AffineTransform saved = g2.getTransform();
1639            g2.translate(insets.left, insets.top);
1640            if (scale) {
1641                AffineTransform st = AffineTransform.getScaleInstance(
1642                        this.scaleX, this.scaleY);
1643                g2.transform(st);
1644            }
1645            this.chart.draw(g2, chartArea, this.anchor, this.info);
1646            g2.setTransform(saved);
1647
1648        }
1649
1650        Iterator iterator = this.overlays.iterator();
1651        while (iterator.hasNext()) {
1652            Overlay overlay = (Overlay) iterator.next();
1653            overlay.paintOverlay(g2, this);
1654        }
1655
1656        // redraw the zoom rectangle (if present) - if useBuffer is false,
1657        // we use XOR so we can XOR the rectangle away again without redrawing
1658        // the chart
1659        drawZoomRectangle(g2, !this.useBuffer);
1660
1661        g2.dispose();
1662
1663        this.anchor = null;
1664        this.verticalTraceLine = null;
1665        this.horizontalTraceLine = null;
1666    }
1667
1668    /**
1669     * Receives notification of changes to the chart, and redraws the chart.
1670     *
1671     * @param event  details of the chart change event.
1672     */
1673    @Override
1674    public void chartChanged(ChartChangeEvent event) {
1675        this.refreshBuffer = true;
1676        Plot plot = this.chart.getPlot();
1677        if (plot instanceof Zoomable) {
1678            Zoomable z = (Zoomable) plot;
1679            this.orientation = z.getOrientation();
1680        }
1681        repaint();
1682    }
1683
1684    /**
1685     * Receives notification of a chart progress event.
1686     *
1687     * @param event  the event.
1688     */
1689    @Override
1690    public void chartProgress(ChartProgressEvent event) {
1691        // does nothing - override if necessary
1692    }
1693
1694    /**
1695     * Handles action events generated by the popup menu.
1696     *
1697     * @param event  the event.
1698     */
1699    @Override
1700    public void actionPerformed(ActionEvent event) {
1701
1702        String command = event.getActionCommand();
1703
1704        // many of the zoom methods need a screen location - all we have is
1705        // the zoomPoint, but it might be null.  Here we grab the x and y
1706        // coordinates, or use defaults...
1707        double screenX = -1.0;
1708        double screenY = -1.0;
1709        if (this.zoomPoint != null) {
1710            screenX = this.zoomPoint.getX();
1711            screenY = this.zoomPoint.getY();
1712        }
1713
1714        if (command.equals(PROPERTIES_COMMAND)) {
1715            doEditChartProperties();
1716        }
1717        else if (command.equals(COPY_COMMAND)) {
1718            doCopy();
1719        }
1720        else if (command.equals(SAVE_AS_PNG_COMMAND)) {
1721            try {
1722                doSaveAs();
1723            }
1724            catch (IOException e) {
1725                JOptionPane.showMessageDialog(this, "I/O error occurred.", 
1726                        "Save As PNG", JOptionPane.WARNING_MESSAGE);
1727            }
1728        }
1729        else if (command.equals(SAVE_AS_SVG_COMMAND)) {
1730            try {
1731                saveAsSVG(null);
1732            } catch (IOException e) {
1733                JOptionPane.showMessageDialog(this, "I/O error occurred.", 
1734                        "Save As SVG", JOptionPane.WARNING_MESSAGE);
1735            }
1736        }
1737        else if (command.equals(SAVE_AS_PDF_COMMAND)) {
1738            saveAsPDF(null);
1739        }
1740        else if (command.equals(PRINT_COMMAND)) {
1741            createChartPrintJob();
1742        }
1743        else if (command.equals(ZOOM_IN_BOTH_COMMAND)) {
1744            zoomInBoth(screenX, screenY);
1745        }
1746        else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) {
1747            zoomInDomain(screenX, screenY);
1748        }
1749        else if (command.equals(ZOOM_IN_RANGE_COMMAND)) {
1750            zoomInRange(screenX, screenY);
1751        }
1752        else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) {
1753            zoomOutBoth(screenX, screenY);
1754        }
1755        else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) {
1756            zoomOutDomain(screenX, screenY);
1757        }
1758        else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) {
1759            zoomOutRange(screenX, screenY);
1760        }
1761        else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) {
1762            restoreAutoBounds();
1763        }
1764        else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) {
1765            restoreAutoDomainBounds();
1766        }
1767        else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) {
1768            restoreAutoRangeBounds();
1769        }
1770
1771    }
1772
1773    /**
1774     * Handles a 'mouse entered' event. This method changes the tooltip delays
1775     * of ToolTipManager.sharedInstance() to the possibly different values set
1776     * for this chart panel.
1777     *
1778     * @param e  the mouse event.
1779     */
1780    @Override
1781    public void mouseEntered(MouseEvent e) {
1782        if (!this.ownToolTipDelaysActive) {
1783            ToolTipManager ttm = ToolTipManager.sharedInstance();
1784
1785            this.originalToolTipInitialDelay = ttm.getInitialDelay();
1786            ttm.setInitialDelay(this.ownToolTipInitialDelay);
1787
1788            this.originalToolTipReshowDelay = ttm.getReshowDelay();
1789            ttm.setReshowDelay(this.ownToolTipReshowDelay);
1790
1791            this.originalToolTipDismissDelay = ttm.getDismissDelay();
1792            ttm.setDismissDelay(this.ownToolTipDismissDelay);
1793
1794            this.ownToolTipDelaysActive = true;
1795        }
1796    }
1797
1798    /**
1799     * Handles a 'mouse exited' event. This method resets the tooltip delays of
1800     * ToolTipManager.sharedInstance() to their
1801     * original values in effect before mouseEntered()
1802     *
1803     * @param e  the mouse event.
1804     */
1805    @Override
1806    public void mouseExited(MouseEvent e) {
1807        if (this.ownToolTipDelaysActive) {
1808            // restore original tooltip dealys
1809            ToolTipManager ttm = ToolTipManager.sharedInstance();
1810            ttm.setInitialDelay(this.originalToolTipInitialDelay);
1811            ttm.setReshowDelay(this.originalToolTipReshowDelay);
1812            ttm.setDismissDelay(this.originalToolTipDismissDelay);
1813            this.ownToolTipDelaysActive = false;
1814        }
1815    }
1816
1817    /**
1818     * Handles a 'mouse pressed' event.
1819     * <P>
1820     * This event is the popup trigger on Unix/Linux.  For Windows, the popup
1821     * trigger is the 'mouse released' event.
1822     *
1823     * @param e  The mouse event.
1824     */
1825    @Override
1826    public void mousePressed(MouseEvent e) {
1827        if (this.chart == null) {
1828            return;
1829        }
1830        Plot plot = this.chart.getPlot();
1831        int mods = e.getModifiers();
1832        if ((mods & this.panMask) == this.panMask) {
1833            // can we pan this plot?
1834            if (plot instanceof Pannable) {
1835                Pannable pannable = (Pannable) plot;
1836                if (pannable.isDomainPannable() || pannable.isRangePannable()) {
1837                    Rectangle2D screenDataArea = getScreenDataArea(e.getX(),
1838                            e.getY());
1839                    if (screenDataArea != null && screenDataArea.contains(
1840                            e.getPoint())) {
1841                        this.panW = screenDataArea.getWidth();
1842                        this.panH = screenDataArea.getHeight();
1843                        this.panLast = e.getPoint();
1844                        setCursor(Cursor.getPredefinedCursor(
1845                                Cursor.MOVE_CURSOR));
1846                    }
1847                }
1848                // the actual panning occurs later in the mouseDragged() 
1849                // method
1850            }
1851        }
1852        else if (this.zoomRectangle == null) {
1853            Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY());
1854            if (screenDataArea != null) {
1855                this.zoomPoint = getPointInRectangle(e.getX(), e.getY(),
1856                        screenDataArea);
1857            }
1858            else {
1859                this.zoomPoint = null;
1860            }
1861            if (e.isPopupTrigger()) {
1862                if (this.popup != null) {
1863                    displayPopupMenu(e.getX(), e.getY());
1864                }
1865            }
1866        }
1867    }
1868
1869    /**
1870     * Returns a point based on (x, y) but constrained to be within the bounds
1871     * of the given rectangle.  This method could be moved to JCommon.
1872     *
1873     * @param x  the x-coordinate.
1874     * @param y  the y-coordinate.
1875     * @param area  the rectangle (<code>null</code> not permitted).
1876     *
1877     * @return A point within the rectangle.
1878     */
1879    private Point2D getPointInRectangle(int x, int y, Rectangle2D area) {
1880        double xx = Math.max(area.getMinX(), Math.min(x, area.getMaxX()));
1881        double yy = Math.max(area.getMinY(), Math.min(y, area.getMaxY()));
1882        return new Point2D.Double(xx, yy);
1883    }
1884
1885    /**
1886     * Handles a 'mouse dragged' event.
1887     *
1888     * @param e  the mouse event.
1889     */
1890    @Override
1891    public void mouseDragged(MouseEvent e) {
1892
1893        // if the popup menu has already been triggered, then ignore dragging...
1894        if (this.popup != null && this.popup.isShowing()) {
1895            return;
1896        }
1897
1898        // handle panning if we have a start point
1899        if (this.panLast != null) {
1900            double dx = e.getX() - this.panLast.getX();
1901            double dy = e.getY() - this.panLast.getY();
1902            if (dx == 0.0 && dy == 0.0) {
1903                return;
1904            }
1905            double wPercent = -dx / this.panW;
1906            double hPercent = dy / this.panH;
1907            boolean old = this.chart.getPlot().isNotify();
1908            this.chart.getPlot().setNotify(false);
1909            Pannable p = (Pannable) this.chart.getPlot();
1910            if (p.getOrientation() == PlotOrientation.VERTICAL) {
1911                p.panDomainAxes(wPercent, this.info.getPlotInfo(),
1912                        this.panLast);
1913                p.panRangeAxes(hPercent, this.info.getPlotInfo(),
1914                        this.panLast);
1915            }
1916            else {
1917                p.panDomainAxes(hPercent, this.info.getPlotInfo(),
1918                        this.panLast);
1919                p.panRangeAxes(wPercent, this.info.getPlotInfo(),
1920                        this.panLast);
1921            }
1922            this.panLast = e.getPoint();
1923            this.chart.getPlot().setNotify(old);
1924            return;
1925        }
1926
1927        // if no initial zoom point was set, ignore dragging...
1928        if (this.zoomPoint == null) {
1929            return;
1930        }
1931        Graphics2D g2 = (Graphics2D) getGraphics();
1932
1933        // erase the previous zoom rectangle (if any).  We only need to do
1934        // this is we are using XOR mode, which we do when we're not using
1935        // the buffer (if there is a buffer, then at the end of this method we
1936        // just trigger a repaint)
1937        if (!this.useBuffer) {
1938            drawZoomRectangle(g2, true);
1939        }
1940
1941        boolean hZoom, vZoom;
1942        if (this.orientation == PlotOrientation.HORIZONTAL) {
1943            hZoom = this.rangeZoomable;
1944            vZoom = this.domainZoomable;
1945        }
1946        else {
1947            hZoom = this.domainZoomable;
1948            vZoom = this.rangeZoomable;
1949        }
1950        Rectangle2D scaledDataArea = getScreenDataArea(
1951                (int) this.zoomPoint.getX(), (int) this.zoomPoint.getY());
1952        if (hZoom && vZoom) {
1953            // selected rectangle shouldn't extend outside the data area...
1954            double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
1955            double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
1956            this.zoomRectangle = new Rectangle2D.Double(
1957                    this.zoomPoint.getX(), this.zoomPoint.getY(),
1958                    xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY());
1959        }
1960        else if (hZoom) {
1961            double xmax = Math.min(e.getX(), scaledDataArea.getMaxX());
1962            this.zoomRectangle = new Rectangle2D.Double(
1963                    this.zoomPoint.getX(), scaledDataArea.getMinY(),
1964                    xmax - this.zoomPoint.getX(), scaledDataArea.getHeight());
1965        }
1966        else if (vZoom) {
1967            double ymax = Math.min(e.getY(), scaledDataArea.getMaxY());
1968            this.zoomRectangle = new Rectangle2D.Double(
1969                    scaledDataArea.getMinX(), this.zoomPoint.getY(),
1970                    scaledDataArea.getWidth(), ymax - this.zoomPoint.getY());
1971        }
1972
1973        // Draw the new zoom rectangle...
1974        if (this.useBuffer) {
1975            repaint();
1976        }
1977        else {
1978            // with no buffer, we use XOR to draw the rectangle "over" the
1979            // chart...
1980            drawZoomRectangle(g2, true);
1981        }
1982        g2.dispose();
1983
1984    }
1985
1986    /**
1987     * Handles a 'mouse released' event.  On Windows, we need to check if this
1988     * is a popup trigger, but only if we haven't already been tracking a zoom
1989     * rectangle.
1990     *
1991     * @param e  information about the event.
1992     */
1993    @Override
1994    public void mouseReleased(MouseEvent e) {
1995
1996        // if we've been panning, we need to reset now that the mouse is 
1997        // released...
1998        if (this.panLast != null) {
1999            this.panLast = null;
2000            setCursor(Cursor.getDefaultCursor());
2001        }
2002
2003        else if (this.zoomRectangle != null) {
2004            boolean hZoom, vZoom;
2005            if (this.orientation == PlotOrientation.HORIZONTAL) {
2006                hZoom = this.rangeZoomable;
2007                vZoom = this.domainZoomable;
2008            }
2009            else {
2010                hZoom = this.domainZoomable;
2011                vZoom = this.rangeZoomable;
2012            }
2013
2014            boolean zoomTrigger1 = hZoom && Math.abs(e.getX()
2015                - this.zoomPoint.getX()) >= this.zoomTriggerDistance;
2016            boolean zoomTrigger2 = vZoom && Math.abs(e.getY()
2017                - this.zoomPoint.getY()) >= this.zoomTriggerDistance;
2018            if (zoomTrigger1 || zoomTrigger2) {
2019                if ((hZoom && (e.getX() < this.zoomPoint.getX()))
2020                    || (vZoom && (e.getY() < this.zoomPoint.getY()))) {
2021                    restoreAutoBounds();
2022                }
2023                else {
2024                    double x, y, w, h;
2025                    Rectangle2D screenDataArea = getScreenDataArea(
2026                            (int) this.zoomPoint.getX(),
2027                            (int) this.zoomPoint.getY());
2028                    double maxX = screenDataArea.getMaxX();
2029                    double maxY = screenDataArea.getMaxY();
2030                    // for mouseReleased event, (horizontalZoom || verticalZoom)
2031                    // will be true, so we can just test for either being false;
2032                    // otherwise both are true
2033                    if (!vZoom) {
2034                        x = this.zoomPoint.getX();
2035                        y = screenDataArea.getMinY();
2036                        w = Math.min(this.zoomRectangle.getWidth(),
2037                                maxX - this.zoomPoint.getX());
2038                        h = screenDataArea.getHeight();
2039                    }
2040                    else if (!hZoom) {
2041                        x = screenDataArea.getMinX();
2042                        y = this.zoomPoint.getY();
2043                        w = screenDataArea.getWidth();
2044                        h = Math.min(this.zoomRectangle.getHeight(),
2045                                maxY - this.zoomPoint.getY());
2046                    }
2047                    else {
2048                        x = this.zoomPoint.getX();
2049                        y = this.zoomPoint.getY();
2050                        w = Math.min(this.zoomRectangle.getWidth(),
2051                                maxX - this.zoomPoint.getX());
2052                        h = Math.min(this.zoomRectangle.getHeight(),
2053                                maxY - this.zoomPoint.getY());
2054                    }
2055                    Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h);
2056                    zoom(zoomArea);
2057                }
2058                this.zoomPoint = null;
2059                this.zoomRectangle = null;
2060            }
2061            else {
2062                // erase the zoom rectangle
2063                Graphics2D g2 = (Graphics2D) getGraphics();
2064                if (this.useBuffer) {
2065                    repaint();
2066                }
2067                else {
2068                    drawZoomRectangle(g2, true);
2069                }
2070                g2.dispose();
2071                this.zoomPoint = null;
2072                this.zoomRectangle = null;
2073            }
2074
2075        }
2076
2077        else if (e.isPopupTrigger()) {
2078            if (this.popup != null) {
2079                displayPopupMenu(e.getX(), e.getY());
2080            }
2081        }
2082
2083    }
2084
2085    /**
2086     * Receives notification of mouse clicks on the panel. These are
2087     * translated and passed on to any registered {@link ChartMouseListener}s.
2088     *
2089     * @param event  Information about the mouse event.
2090     */
2091    @Override
2092    public void mouseClicked(MouseEvent event) {
2093
2094        Insets insets = getInsets();
2095        int x = (int) ((event.getX() - insets.left) / this.scaleX);
2096        int y = (int) ((event.getY() - insets.top) / this.scaleY);
2097
2098        this.anchor = new Point2D.Double(x, y);
2099        if (this.chart == null) {
2100            return;
2101        }
2102        this.chart.setNotify(true);  // force a redraw
2103        // new entity code...
2104        Object[] listeners = this.chartMouseListeners.getListeners(
2105                ChartMouseListener.class);
2106        if (listeners.length == 0) {
2107            return;
2108        }
2109
2110        ChartEntity entity = null;
2111        if (this.info != null) {
2112            EntityCollection entities = this.info.getEntityCollection();
2113            if (entities != null) {
2114                entity = entities.getEntity(x, y);
2115            }
2116        }
2117        ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event,
2118                entity);
2119        for (int i = listeners.length - 1; i >= 0; i -= 1) {
2120            ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent);
2121        }
2122
2123    }
2124
2125    /**
2126     * Implementation of the MouseMotionListener's method.
2127     *
2128     * @param e  the event.
2129     */
2130    @Override
2131    public void mouseMoved(MouseEvent e) {
2132        Graphics2D g2 = (Graphics2D) getGraphics();
2133        if (this.horizontalAxisTrace) {
2134            drawHorizontalAxisTrace(g2, e.getX());
2135        }
2136        if (this.verticalAxisTrace) {
2137            drawVerticalAxisTrace(g2, e.getY());
2138        }
2139        g2.dispose();
2140
2141        Object[] listeners = this.chartMouseListeners.getListeners(
2142                ChartMouseListener.class);
2143        if (listeners.length == 0) {
2144            return;
2145        }
2146        Insets insets = getInsets();
2147        int x = (int) ((e.getX() - insets.left) / this.scaleX);
2148        int y = (int) ((e.getY() - insets.top) / this.scaleY);
2149
2150        ChartEntity entity = null;
2151        if (this.info != null) {
2152            EntityCollection entities = this.info.getEntityCollection();
2153            if (entities != null) {
2154                entity = entities.getEntity(x, y);
2155            }
2156        }
2157
2158        // we can only generate events if the panel's chart is not null
2159        // (see bug report 1556951)
2160        if (this.chart != null) {
2161            ChartMouseEvent event = new ChartMouseEvent(getChart(), e, entity);
2162            for (int i = listeners.length - 1; i >= 0; i -= 1) {
2163                ((ChartMouseListener) listeners[i]).chartMouseMoved(event);
2164            }
2165        }
2166
2167    }
2168
2169    /**
2170     * Zooms in on an anchor point (specified in screen coordinate space).
2171     *
2172     * @param x  the x value (in screen coordinates).
2173     * @param y  the y value (in screen coordinates).
2174     */
2175    public void zoomInBoth(double x, double y) {
2176        Plot plot = this.chart.getPlot();
2177        if (plot == null) {
2178            return;
2179        }
2180        // here we tweak the notify flag on the plot so that only
2181        // one notification happens even though we update multiple
2182        // axes...
2183        boolean savedNotify = plot.isNotify();
2184        plot.setNotify(false);
2185        zoomInDomain(x, y);
2186        zoomInRange(x, y);
2187        plot.setNotify(savedNotify);
2188    }
2189
2190    /**
2191     * Decreases the length of the domain axis, centered about the given
2192     * coordinate on the screen.  The length of the domain axis is reduced
2193     * by the value of {@link #getZoomInFactor()}.
2194     *
2195     * @param x  the x coordinate (in screen coordinates).
2196     * @param y  the y-coordinate (in screen coordinates).
2197     */
2198    public void zoomInDomain(double x, double y) {
2199        Plot plot = this.chart.getPlot();
2200        if (plot instanceof Zoomable) {
2201            // here we tweak the notify flag on the plot so that only
2202            // one notification happens even though we update multiple
2203            // axes...
2204            boolean savedNotify = plot.isNotify();
2205            plot.setNotify(false);
2206            Zoomable z = (Zoomable) plot;
2207            z.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(),
2208                    translateScreenToJava2D(new Point((int) x, (int) y)),
2209                    this.zoomAroundAnchor);
2210            plot.setNotify(savedNotify);
2211        }
2212    }
2213
2214    /**
2215     * Decreases the length of the range axis, centered about the given
2216     * coordinate on the screen.  The length of the range axis is reduced by
2217     * the value of {@link #getZoomInFactor()}.
2218     *
2219     * @param x  the x-coordinate (in screen coordinates).
2220     * @param y  the y coordinate (in screen coordinates).
2221     */
2222    public void zoomInRange(double x, double y) {
2223        Plot plot = this.chart.getPlot();
2224        if (plot instanceof Zoomable) {
2225            // here we tweak the notify flag on the plot so that only
2226            // one notification happens even though we update multiple
2227            // axes...
2228            boolean savedNotify = plot.isNotify();
2229            plot.setNotify(false);
2230            Zoomable z = (Zoomable) plot;
2231            z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(),
2232                    translateScreenToJava2D(new Point((int) x, (int) y)),
2233                    this.zoomAroundAnchor);
2234            plot.setNotify(savedNotify);
2235        }
2236    }
2237
2238    /**
2239     * Zooms out on an anchor point (specified in screen coordinate space).
2240     *
2241     * @param x  the x value (in screen coordinates).
2242     * @param y  the y value (in screen coordinates).
2243     */
2244    public void zoomOutBoth(double x, double y) {
2245        Plot plot = this.chart.getPlot();
2246        if (plot == null) {
2247            return;
2248        }
2249        // here we tweak the notify flag on the plot so that only
2250        // one notification happens even though we update multiple
2251        // axes...
2252        boolean savedNotify = plot.isNotify();
2253        plot.setNotify(false);
2254        zoomOutDomain(x, y);
2255        zoomOutRange(x, y);
2256        plot.setNotify(savedNotify);
2257    }
2258
2259    /**
2260     * Increases the length of the domain axis, centered about the given
2261     * coordinate on the screen.  The length of the domain axis is increased
2262     * by the value of {@link #getZoomOutFactor()}.
2263     *
2264     * @param x  the x coordinate (in screen coordinates).
2265     * @param y  the y-coordinate (in screen coordinates).
2266     */
2267    public void zoomOutDomain(double x, double y) {
2268        Plot plot = this.chart.getPlot();
2269        if (plot instanceof Zoomable) {
2270            // here we tweak the notify flag on the plot so that only
2271            // one notification happens even though we update multiple
2272            // axes...
2273            boolean savedNotify = plot.isNotify();
2274            plot.setNotify(false);
2275            Zoomable z = (Zoomable) plot;
2276            z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(),
2277                    translateScreenToJava2D(new Point((int) x, (int) y)),
2278                    this.zoomAroundAnchor);
2279            plot.setNotify(savedNotify);
2280        }
2281    }
2282
2283    /**
2284     * Increases the length the range axis, centered about the given
2285     * coordinate on the screen.  The length of the range axis is increased
2286     * by the value of {@link #getZoomOutFactor()}.
2287     *
2288     * @param x  the x coordinate (in screen coordinates).
2289     * @param y  the y-coordinate (in screen coordinates).
2290     */
2291    public void zoomOutRange(double x, double y) {
2292        Plot plot = this.chart.getPlot();
2293        if (plot instanceof Zoomable) {
2294            // here we tweak the notify flag on the plot so that only
2295            // one notification happens even though we update multiple
2296            // axes...
2297            boolean savedNotify = plot.isNotify();
2298            plot.setNotify(false);
2299            Zoomable z = (Zoomable) plot;
2300            z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(),
2301                    translateScreenToJava2D(new Point((int) x, (int) y)),
2302                    this.zoomAroundAnchor);
2303            plot.setNotify(savedNotify);
2304        }
2305    }
2306
2307    /**
2308     * Zooms in on a selected region.
2309     *
2310     * @param selection  the selected region.
2311     */
2312    public void zoom(Rectangle2D selection) {
2313
2314        // get the origin of the zoom selection in the Java2D space used for
2315        // drawing the chart (that is, before any scaling to fit the panel)
2316        Point2D selectOrigin = translateScreenToJava2D(new Point(
2317                (int) Math.ceil(selection.getX()),
2318                (int) Math.ceil(selection.getY())));
2319        PlotRenderingInfo plotInfo = this.info.getPlotInfo();
2320        Rectangle2D scaledDataArea = getScreenDataArea(
2321                (int) selection.getCenterX(), (int) selection.getCenterY());
2322        if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) {
2323
2324            double hLower = (selection.getMinX() - scaledDataArea.getMinX())
2325                / scaledDataArea.getWidth();
2326            double hUpper = (selection.getMaxX() - scaledDataArea.getMinX())
2327                / scaledDataArea.getWidth();
2328            double vLower = (scaledDataArea.getMaxY() - selection.getMaxY())
2329                / scaledDataArea.getHeight();
2330            double vUpper = (scaledDataArea.getMaxY() - selection.getMinY())
2331                / scaledDataArea.getHeight();
2332
2333            Plot p = this.chart.getPlot();
2334            if (p instanceof Zoomable) {
2335                // here we tweak the notify flag on the plot so that only
2336                // one notification happens even though we update multiple
2337                // axes...
2338                boolean savedNotify = p.isNotify();
2339                p.setNotify(false);
2340                Zoomable z = (Zoomable) p;
2341                if (z.getOrientation() == PlotOrientation.HORIZONTAL) {
2342                    z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin);
2343                    z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin);
2344                }
2345                else {
2346                    z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin);
2347                    z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin);
2348                }
2349                p.setNotify(savedNotify);
2350            }
2351
2352        }
2353
2354    }
2355
2356    /**
2357     * Restores the auto-range calculation on both axes.
2358     */
2359    public void restoreAutoBounds() {
2360        Plot plot = this.chart.getPlot();
2361        if (plot == null) {
2362            return;
2363        }
2364        // here we tweak the notify flag on the plot so that only
2365        // one notification happens even though we update multiple
2366        // axes...
2367        boolean savedNotify = plot.isNotify();
2368        plot.setNotify(false);
2369        restoreAutoDomainBounds();
2370        restoreAutoRangeBounds();
2371        plot.setNotify(savedNotify);
2372    }
2373
2374    /**
2375     * Restores the auto-range calculation on the domain axis.
2376     */
2377    public void restoreAutoDomainBounds() {
2378        Plot plot = this.chart.getPlot();
2379        if (plot instanceof Zoomable) {
2380            Zoomable z = (Zoomable) plot;
2381            // here we tweak the notify flag on the plot so that only
2382            // one notification happens even though we update multiple
2383            // axes...
2384            boolean savedNotify = plot.isNotify();
2385            plot.setNotify(false);
2386            // we need to guard against this.zoomPoint being null
2387            Point2D zp = (this.zoomPoint != null
2388                    ? this.zoomPoint : new Point());
2389            z.zoomDomainAxes(0.0, this.info.getPlotInfo(), zp);
2390            plot.setNotify(savedNotify);
2391        }
2392    }
2393
2394    /**
2395     * Restores the auto-range calculation on the range axis.
2396     */
2397    public void restoreAutoRangeBounds() {
2398        Plot plot = this.chart.getPlot();
2399        if (plot instanceof Zoomable) {
2400            Zoomable z = (Zoomable) plot;
2401            // here we tweak the notify flag on the plot so that only
2402            // one notification happens even though we update multiple
2403            // axes...
2404            boolean savedNotify = plot.isNotify();
2405            plot.setNotify(false);
2406            // we need to guard against this.zoomPoint being null
2407            Point2D zp = (this.zoomPoint != null
2408                    ? this.zoomPoint : new Point());
2409            z.zoomRangeAxes(0.0, this.info.getPlotInfo(), zp);
2410            plot.setNotify(savedNotify);
2411        }
2412    }
2413
2414    /**
2415     * Returns the data area for the chart (the area inside the axes) with the
2416     * current scaling applied (that is, the area as it appears on screen).
2417     *
2418     * @return The scaled data area.
2419     */
2420    public Rectangle2D getScreenDataArea() {
2421        Rectangle2D dataArea = this.info.getPlotInfo().getDataArea();
2422        Insets insets = getInsets();
2423        double x = dataArea.getX() * this.scaleX + insets.left;
2424        double y = dataArea.getY() * this.scaleY + insets.top;
2425        double w = dataArea.getWidth() * this.scaleX;
2426        double h = dataArea.getHeight() * this.scaleY;
2427        return new Rectangle2D.Double(x, y, w, h);
2428    }
2429
2430    /**
2431     * Returns the data area (the area inside the axes) for the plot or subplot,
2432     * with the current scaling applied.
2433     *
2434     * @param x  the x-coordinate (for subplot selection).
2435     * @param y  the y-coordinate (for subplot selection).
2436     *
2437     * @return The scaled data area.
2438     */
2439    public Rectangle2D getScreenDataArea(int x, int y) {
2440        PlotRenderingInfo plotInfo = this.info.getPlotInfo();
2441        Rectangle2D result;
2442        if (plotInfo.getSubplotCount() == 0) {
2443            result = getScreenDataArea();
2444        }
2445        else {
2446            // get the origin of the zoom selection in the Java2D space used for
2447            // drawing the chart (that is, before any scaling to fit the panel)
2448            Point2D selectOrigin = translateScreenToJava2D(new Point(x, y));
2449            int subplotIndex = plotInfo.getSubplotIndex(selectOrigin);
2450            if (subplotIndex == -1) {
2451                return null;
2452            }
2453            result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea());
2454        }
2455        return result;
2456    }
2457
2458    /**
2459     * Returns the initial tooltip delay value used inside this chart panel.
2460     *
2461     * @return An integer representing the initial delay value, in milliseconds.
2462     *
2463     * @see javax.swing.ToolTipManager#getInitialDelay()
2464     */
2465    public int getInitialDelay() {
2466        return this.ownToolTipInitialDelay;
2467    }
2468
2469    /**
2470     * Returns the reshow tooltip delay value used inside this chart panel.
2471     *
2472     * @return An integer representing the reshow  delay value, in milliseconds.
2473     *
2474     * @see javax.swing.ToolTipManager#getReshowDelay()
2475     */
2476    public int getReshowDelay() {
2477        return this.ownToolTipReshowDelay;
2478    }
2479
2480    /**
2481     * Returns the dismissal tooltip delay value used inside this chart panel.
2482     *
2483     * @return An integer representing the dismissal delay value, in
2484     *         milliseconds.
2485     *
2486     * @see javax.swing.ToolTipManager#getDismissDelay()
2487     */
2488    public int getDismissDelay() {
2489        return this.ownToolTipDismissDelay;
2490    }
2491
2492    /**
2493     * Specifies the initial delay value for this chart panel.
2494     *
2495     * @param delay  the number of milliseconds to delay (after the cursor has
2496     *               paused) before displaying.
2497     *
2498     * @see javax.swing.ToolTipManager#setInitialDelay(int)
2499     */
2500    public void setInitialDelay(int delay) {
2501        this.ownToolTipInitialDelay = delay;
2502    }
2503
2504    /**
2505     * Specifies the amount of time before the user has to wait initialDelay
2506     * milliseconds before a tooltip will be shown.
2507     *
2508     * @param delay  time in milliseconds
2509     *
2510     * @see javax.swing.ToolTipManager#setReshowDelay(int)
2511     */
2512    public void setReshowDelay(int delay) {
2513        this.ownToolTipReshowDelay = delay;
2514    }
2515
2516    /**
2517     * Specifies the dismissal delay value for this chart panel.
2518     *
2519     * @param delay the number of milliseconds to delay before taking away the
2520     *              tooltip
2521     *
2522     * @see javax.swing.ToolTipManager#setDismissDelay(int)
2523     */
2524    public void setDismissDelay(int delay) {
2525        this.ownToolTipDismissDelay = delay;
2526    }
2527
2528    /**
2529     * Returns the zoom in factor.
2530     *
2531     * @return The zoom in factor.
2532     *
2533     * @see #setZoomInFactor(double)
2534     */
2535    public double getZoomInFactor() {
2536        return this.zoomInFactor;
2537    }
2538
2539    /**
2540     * Sets the zoom in factor.
2541     *
2542     * @param factor  the factor.
2543     *
2544     * @see #getZoomInFactor()
2545     */
2546    public void setZoomInFactor(double factor) {
2547        this.zoomInFactor = factor;
2548    }
2549
2550    /**
2551     * Returns the zoom out factor.
2552     *
2553     * @return The zoom out factor.
2554     *
2555     * @see #setZoomOutFactor(double)
2556     */
2557    public double getZoomOutFactor() {
2558        return this.zoomOutFactor;
2559    }
2560
2561    /**
2562     * Sets the zoom out factor.
2563     *
2564     * @param factor  the factor.
2565     *
2566     * @see #getZoomOutFactor()
2567     */
2568    public void setZoomOutFactor(double factor) {
2569        this.zoomOutFactor = factor;
2570    }
2571
2572    /**
2573     * Draws zoom rectangle (if present).
2574     * The drawing is performed in XOR mode, therefore
2575     * when this method is called twice in a row,
2576     * the second call will completely restore the state
2577     * of the canvas.
2578     *
2579     * @param g2 the graphics device.
2580     * @param xor  use XOR for drawing?
2581     */
2582    private void drawZoomRectangle(Graphics2D g2, boolean xor) {
2583        if (this.zoomRectangle != null) {
2584            if (xor) {
2585                 // Set XOR mode to draw the zoom rectangle
2586                g2.setXORMode(Color.gray);
2587            }
2588            if (this.fillZoomRectangle) {
2589                g2.setPaint(this.zoomFillPaint);
2590                g2.fill(this.zoomRectangle);
2591            }
2592            else {
2593                g2.setPaint(this.zoomOutlinePaint);
2594                g2.draw(this.zoomRectangle);
2595            }
2596            if (xor) {
2597                // Reset to the default 'overwrite' mode
2598                g2.setPaintMode();
2599            }
2600        }
2601    }
2602
2603    /**
2604     * Draws a vertical line used to trace the mouse position to the horizontal
2605     * axis.
2606     *
2607     * @param g2 the graphics device.
2608     * @param x  the x-coordinate of the trace line.
2609     */
2610    private void drawHorizontalAxisTrace(Graphics2D g2, int x) {
2611
2612        Rectangle2D dataArea = getScreenDataArea();
2613
2614        g2.setXORMode(Color.orange);
2615        if (((int) dataArea.getMinX() < x) && (x < (int) dataArea.getMaxX())) {
2616
2617            if (this.verticalTraceLine != null) {
2618                g2.draw(this.verticalTraceLine);
2619                this.verticalTraceLine.setLine(x, (int) dataArea.getMinY(), x,
2620                        (int) dataArea.getMaxY());
2621            }
2622            else {
2623                this.verticalTraceLine = new Line2D.Float(x,
2624                        (int) dataArea.getMinY(), x, (int) dataArea.getMaxY());
2625            }
2626            g2.draw(this.verticalTraceLine);
2627        }
2628
2629        // Reset to the default 'overwrite' mode
2630        g2.setPaintMode();
2631    }
2632
2633    /**
2634     * Draws a horizontal line used to trace the mouse position to the vertical
2635     * axis.
2636     *
2637     * @param g2 the graphics device.
2638     * @param y  the y-coordinate of the trace line.
2639     */
2640    private void drawVerticalAxisTrace(Graphics2D g2, int y) {
2641
2642        Rectangle2D dataArea = getScreenDataArea();
2643
2644        g2.setXORMode(Color.orange);
2645        if (((int) dataArea.getMinY() < y) && (y < (int) dataArea.getMaxY())) {
2646
2647            if (this.horizontalTraceLine != null) {
2648                g2.draw(this.horizontalTraceLine);
2649                this.horizontalTraceLine.setLine((int) dataArea.getMinX(), y,
2650                        (int) dataArea.getMaxX(), y);
2651            }
2652            else {
2653                this.horizontalTraceLine = new Line2D.Float(
2654                        (int) dataArea.getMinX(), y, (int) dataArea.getMaxX(),
2655                        y);
2656            }
2657            g2.draw(this.horizontalTraceLine);
2658        }
2659
2660        // Reset to the default 'overwrite' mode
2661        g2.setPaintMode();
2662    }
2663
2664    /**
2665     * Displays a dialog that allows the user to edit the properties for the
2666     * current chart.
2667     *
2668     * @since 1.0.3
2669     */
2670    public void doEditChartProperties() {
2671
2672        ChartEditor editor = ChartEditorManager.getChartEditor(this.chart);
2673        int result = JOptionPane.showConfirmDialog(this, editor,
2674                localizationResources.getString("Chart_Properties"),
2675                JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
2676        if (result == JOptionPane.OK_OPTION) {
2677            editor.updateChart(this.chart);
2678        }
2679
2680    }
2681
2682    /**
2683     * Copies the current chart to the system clipboard.
2684     * 
2685     * @since 1.0.13
2686     */
2687    public void doCopy() {
2688        Clipboard systemClipboard
2689                = Toolkit.getDefaultToolkit().getSystemClipboard();
2690        Insets insets = getInsets();
2691        int w = getWidth() - insets.left - insets.right;
2692        int h = getHeight() - insets.top - insets.bottom;
2693        ChartTransferable selection = new ChartTransferable(this.chart, w, h,
2694                getMinimumDrawWidth(), getMinimumDrawHeight(),
2695                getMaximumDrawWidth(), getMaximumDrawHeight(), true);
2696        systemClipboard.setContents(selection, null);
2697    }
2698
2699    /**
2700     * Opens a file chooser and gives the user an opportunity to save the chart
2701     * in PNG format.
2702     *
2703     * @throws IOException if there is an I/O error.
2704     */
2705    public void doSaveAs() throws IOException {
2706        JFileChooser fileChooser = new JFileChooser();
2707        fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
2708        FileNameExtensionFilter filter = new FileNameExtensionFilter(
2709                    localizationResources.getString("PNG_Image_Files"), "png");
2710        fileChooser.addChoosableFileFilter(filter);
2711        fileChooser.setFileFilter(filter);
2712
2713        int option = fileChooser.showSaveDialog(this);
2714        if (option == JFileChooser.APPROVE_OPTION) {
2715            String filename = fileChooser.getSelectedFile().getPath();
2716            if (isEnforceFileExtensions()) {
2717                if (!filename.endsWith(".png")) {
2718                    filename = filename + ".png";
2719                }
2720            }
2721            ChartUtilities.saveChartAsPNG(new File(filename), this.chart,
2722                    getWidth(), getHeight());
2723        }
2724    }
2725    
2726    /**
2727     * Saves the chart in SVG format (a filechooser will be displayed so that
2728     * the user can specify the filename).  Note that this method only works
2729     * if the JFreeSVG library is on the classpath...if this library is not 
2730     * present, the method will fail.
2731     */
2732    private void saveAsSVG(File f) throws IOException {
2733        File file = f;
2734        if (file == null) {
2735            JFileChooser fileChooser = new JFileChooser();
2736            fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
2737            FileNameExtensionFilter filter = new FileNameExtensionFilter(
2738                    localizationResources.getString("SVG_Files"), "svg");
2739            fileChooser.addChoosableFileFilter(filter);
2740            fileChooser.setFileFilter(filter);
2741
2742            int option = fileChooser.showSaveDialog(this);
2743            if (option == JFileChooser.APPROVE_OPTION) {
2744                String filename = fileChooser.getSelectedFile().getPath();
2745                if (isEnforceFileExtensions()) {
2746                    if (!filename.endsWith(".svg")) {
2747                        filename = filename + ".svg";
2748                    }
2749                }
2750                file = new File(filename);
2751                if (file.exists()) {
2752                    String fileExists = localizationResources.getString(
2753                            "FILE_EXISTS_CONFIRM_OVERWRITE");
2754                    int response = JOptionPane.showConfirmDialog(this, 
2755                            fileExists, "Save As SVG", 
2756                            JOptionPane.OK_CANCEL_OPTION);
2757                    if (response == JOptionPane.CANCEL_OPTION) {
2758                        file = null;
2759                    }
2760                }
2761            }
2762        }
2763        
2764        if (file != null) {
2765            // use reflection to get the SVG string
2766            String svg = generateSVG(getWidth(), getHeight());
2767            BufferedWriter writer = null;
2768            try {
2769                writer = new BufferedWriter(new FileWriter(file));
2770                writer.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
2771                writer.write(svg + "\n");
2772                writer.flush();
2773            } finally {
2774                try {
2775                    if (writer != null) {
2776                        writer.close();
2777                    }
2778                } catch (IOException ex) {
2779                    throw new RuntimeException(ex);
2780                }
2781            } 
2782
2783        }
2784    }
2785    
2786    /**
2787     * Generates a string containing a rendering of the chart in SVG format.
2788     * This feature is only supported if the JFreeSVG library is included on 
2789     * the classpath.
2790     * 
2791     * @return A string containing an SVG element for the current chart, or 
2792     *     <code>null</code> if there is a problem with the method invocation
2793     *     by reflection.
2794     */
2795    private String generateSVG(int width, int height) {
2796        Graphics2D g2 = createSVGGraphics2D(width, height);
2797        if (g2 == null) {
2798            throw new IllegalStateException("JFreeSVG library is not present.");
2799        }
2800        // we suppress shadow generation, because SVG is a vector format and
2801        // the shadow effect is applied via bitmap effects...
2802        g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true);
2803        String svg = null;
2804        Rectangle2D drawArea = new Rectangle2D.Double(0, 0, width, height);
2805        this.chart.draw(g2, drawArea);
2806        try {
2807            Method m = g2.getClass().getMethod("getSVGElement");
2808            svg = (String) m.invoke(g2);
2809        } catch (NoSuchMethodException e) {
2810            // null will be returned
2811        } catch (SecurityException e) {
2812            // null will be returned
2813        } catch (IllegalAccessException e) {
2814            // null will be returned
2815        } catch (IllegalArgumentException e) {
2816            // null will be returned
2817        } catch (InvocationTargetException e) {
2818            // null will be returned
2819        }
2820        return svg;
2821    }
2822
2823    private Graphics2D createSVGGraphics2D(int w, int h) {
2824        try {
2825            Class svgGraphics2d = Class.forName("org.jfree.graphics2d.svg.SVGGraphics2D");
2826            Constructor ctor = svgGraphics2d.getConstructor(int.class, int.class);
2827            return (Graphics2D) ctor.newInstance(w, h);
2828        } catch (ClassNotFoundException ex) {
2829            return null;
2830        } catch (NoSuchMethodException ex) {
2831            return null;
2832        } catch (SecurityException ex) {
2833            return null;
2834        } catch (InstantiationException ex) {
2835            return null;
2836        } catch (IllegalAccessException ex) {
2837            return null;
2838        } catch (IllegalArgumentException ex) {
2839            return null;
2840        } catch (InvocationTargetException ex) {
2841            return null;
2842        }
2843    }
2844
2845    /**
2846     * Saves the chart in PDF format (a filechooser will be displayed so that
2847     * the user can specify the filename).  Note that this method only works
2848     * if the OrsonPDF library is on the classpath...if this library is not
2849     * present, the method will fail.
2850     */
2851    private void saveAsPDF(File f) {
2852        File file = f;
2853        if (file == null) {
2854            JFileChooser fileChooser = new JFileChooser();
2855            fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs);
2856            FileNameExtensionFilter filter = new FileNameExtensionFilter(
2857                    localizationResources.getString("PDF_Files"), "pdf");
2858            fileChooser.addChoosableFileFilter(filter);
2859            fileChooser.setFileFilter(filter);
2860
2861            int option = fileChooser.showSaveDialog(this);
2862            if (option == JFileChooser.APPROVE_OPTION) {
2863                String filename = fileChooser.getSelectedFile().getPath();
2864                if (isEnforceFileExtensions()) {
2865                    if (!filename.endsWith(".pdf")) {
2866                        filename = filename + ".pdf";
2867                    }
2868                }
2869                file = new File(filename);
2870                if (file.exists()) {
2871                    String fileExists = localizationResources.getString(
2872                            "FILE_EXISTS_CONFIRM_OVERWRITE");
2873                    int response = JOptionPane.showConfirmDialog(this, 
2874                            fileExists, "Save As PDF", 
2875                            JOptionPane.OK_CANCEL_OPTION);
2876                    if (response == JOptionPane.CANCEL_OPTION) {
2877                        file = null;
2878                    }
2879                }
2880            }
2881        }
2882        
2883        if (file != null) {
2884            writeAsPDF(file, getWidth(), getHeight());
2885        }
2886    }
2887
2888    /**
2889     * Returns <code>true</code> if OrsonPDF is on the classpath, and 
2890     * <code>false</code> otherwise.  The OrsonPDF library can be found at
2891     * http://www.object-refinery.com/pdf/
2892     * 
2893     * @return A boolean.
2894     */
2895    private boolean isOrsonPDFAvailable() {
2896        Class pdfDocumentClass = null;
2897        try {
2898            pdfDocumentClass = Class.forName("com.orsonpdf.PDFDocument");
2899        } catch (ClassNotFoundException e) {
2900            // pdfDocument class will be null so the function will return false
2901        }
2902        return (pdfDocumentClass != null);
2903    }
2904    
2905    /**
2906     * Writes the current chart to the specified file in PDF format.  This 
2907     * will only work when the OrsonPDF library is found on the classpath.
2908     * Reflection is used to ensure there is no compile-time dependency on
2909     * OrsonPDF (which is non-free software).
2910     * 
2911     * @param file  the output file (<code>null</code> not permitted).
2912     * @param w  the chart width.
2913     * @param h  the chart height.
2914     */
2915    private void writeAsPDF(File file, int w, int h) {
2916        if (!isOrsonPDFAvailable()) {
2917            throw new IllegalStateException(
2918                    "OrsonPDF is not present on the classpath.");
2919        }
2920        ParamChecks.nullNotPermitted(file, "file");
2921        try {
2922            Class pdfDocClass = Class.forName("com.orsonpdf.PDFDocument");
2923            Object pdfDoc = pdfDocClass.newInstance();
2924            Method m = pdfDocClass.getMethod("createPage", Rectangle2D.class);
2925            Rectangle2D rect = new Rectangle(w, h);
2926            Object page = m.invoke(pdfDoc, rect);
2927            Method m2 = page.getClass().getMethod("getGraphics2D");
2928            Graphics2D g2 = (Graphics2D) m2.invoke(page);
2929            // we suppress shadow generation, because PDF is a vector format and
2930            // the shadow effect is applied via bitmap effects...
2931            g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true);
2932            Rectangle2D drawArea = new Rectangle2D.Double(0, 0, w, h);
2933            this.chart.draw(g2, drawArea);
2934            Method m3 = pdfDocClass.getMethod("writeToFile", File.class);
2935            m3.invoke(pdfDoc, file);
2936        } catch (ClassNotFoundException ex) {
2937            throw new RuntimeException(ex);
2938        } catch (InstantiationException ex) {
2939            throw new RuntimeException(ex);
2940        } catch (IllegalAccessException ex) {
2941            throw new RuntimeException(ex);
2942        } catch (NoSuchMethodException ex) {
2943            throw new RuntimeException(ex);
2944        } catch (SecurityException ex) {
2945            throw new RuntimeException(ex);
2946        } catch (IllegalArgumentException ex) {
2947            throw new RuntimeException(ex);
2948        } catch (InvocationTargetException ex) {
2949            throw new RuntimeException(ex);
2950        }
2951    }
2952
2953    /**
2954     * Creates a print job for the chart.
2955     */
2956    public void createChartPrintJob() {
2957        PrinterJob job = PrinterJob.getPrinterJob();
2958        PageFormat pf = job.defaultPage();
2959        PageFormat pf2 = job.pageDialog(pf);
2960        if (pf2 != pf) {
2961            job.setPrintable(this, pf2);
2962            if (job.printDialog()) {
2963                try {
2964                    job.print();
2965                }
2966                catch (PrinterException e) {
2967                    JOptionPane.showMessageDialog(this, e);
2968                }
2969            }
2970        }
2971    }
2972
2973    /**
2974     * Prints the chart on a single page.
2975     *
2976     * @param g  the graphics context.
2977     * @param pf  the page format to use.
2978     * @param pageIndex  the index of the page. If not <code>0</code>, nothing
2979     *                   gets print.
2980     *
2981     * @return The result of printing.
2982     */
2983    @Override
2984    public int print(Graphics g, PageFormat pf, int pageIndex) {
2985
2986        if (pageIndex != 0) {
2987            return NO_SUCH_PAGE;
2988        }
2989        Graphics2D g2 = (Graphics2D) g;
2990        double x = pf.getImageableX();
2991        double y = pf.getImageableY();
2992        double w = pf.getImageableWidth();
2993        double h = pf.getImageableHeight();
2994        this.chart.draw(g2, new Rectangle2D.Double(x, y, w, h), this.anchor,
2995                null);
2996        return PAGE_EXISTS;
2997
2998    }
2999
3000    /**
3001     * Adds a listener to the list of objects listening for chart mouse events.
3002     *
3003     * @param listener  the listener (<code>null</code> not permitted).
3004     */
3005    public void addChartMouseListener(ChartMouseListener listener) {
3006        ParamChecks.nullNotPermitted(listener, "listener");
3007        this.chartMouseListeners.add(ChartMouseListener.class, listener);
3008    }
3009
3010    /**
3011     * Removes a listener from the list of objects listening for chart mouse
3012     * events.
3013     *
3014     * @param listener  the listener.
3015     */
3016    public void removeChartMouseListener(ChartMouseListener listener) {
3017        this.chartMouseListeners.remove(ChartMouseListener.class, listener);
3018    }
3019
3020    /**
3021     * Returns an array of the listeners of the given type registered with the
3022     * panel.
3023     *
3024     * @param listenerType  the listener type.
3025     *
3026     * @return An array of listeners.
3027     */
3028    @Override
3029    public EventListener[] getListeners(Class listenerType) {
3030        if (listenerType == ChartMouseListener.class) {
3031            // fetch listeners from local storage
3032            return this.chartMouseListeners.getListeners(listenerType);
3033        }
3034        else {
3035            return super.getListeners(listenerType);
3036        }
3037    }
3038
3039    /**
3040     * Creates a popup menu for the panel.
3041     *
3042     * @param properties  include a menu item for the chart property editor.
3043     * @param save  include a menu item for saving the chart.
3044     * @param print  include a menu item for printing the chart.
3045     * @param zoom  include menu items for zooming.
3046     *
3047     * @return The popup menu.
3048     */
3049    protected JPopupMenu createPopupMenu(boolean properties, boolean save,
3050            boolean print, boolean zoom) {
3051        return createPopupMenu(properties, false, save, print, zoom);
3052    }
3053
3054    /**
3055     * Creates a popup menu for the panel.
3056     *
3057     * @param properties  include a menu item for the chart property editor.
3058     * @param copy include a menu item for copying to the clipboard.
3059     * @param save  include a menu item for saving the chart.
3060     * @param print  include a menu item for printing the chart.
3061     * @param zoom  include menu items for zooming.
3062     *
3063     * @return The popup menu.
3064     *
3065     * @since 1.0.13
3066     */
3067    protected JPopupMenu createPopupMenu(boolean properties,
3068            boolean copy, boolean save, boolean print, boolean zoom) {
3069
3070        JPopupMenu result = new JPopupMenu(localizationResources.getString("Chart") + ":");
3071        boolean separator = false;
3072
3073        if (properties) {
3074            JMenuItem propertiesItem = new JMenuItem(
3075                    localizationResources.getString("Properties..."));
3076            propertiesItem.setActionCommand(PROPERTIES_COMMAND);
3077            propertiesItem.addActionListener(this);
3078            result.add(propertiesItem);
3079            separator = true;
3080        }
3081
3082        if (copy) {
3083            if (separator) {
3084                result.addSeparator();
3085            }
3086            JMenuItem copyItem = new JMenuItem(
3087                    localizationResources.getString("Copy"));
3088            copyItem.setActionCommand(COPY_COMMAND);
3089            copyItem.addActionListener(this);
3090            result.add(copyItem);
3091            separator = !save;
3092        }
3093
3094        if (save) {
3095            if (separator) {
3096                result.addSeparator();
3097            }
3098            JMenu saveSubMenu = new JMenu(localizationResources.getString(
3099                    "Save_as"));
3100            JMenuItem pngItem = new JMenuItem(localizationResources.getString(
3101                    "PNG..."));
3102            pngItem.setActionCommand("SAVE_AS_PNG");
3103            pngItem.addActionListener(this);
3104            saveSubMenu.add(pngItem);
3105            
3106            if (createSVGGraphics2D(10, 10) != null) {
3107                JMenuItem svgItem = new JMenuItem(localizationResources.getString(
3108                        "SVG..."));
3109                svgItem.setActionCommand("SAVE_AS_SVG");
3110                svgItem.addActionListener(this);
3111                saveSubMenu.add(svgItem);                
3112            }
3113            
3114            if (isOrsonPDFAvailable()) {
3115                JMenuItem pdfItem = new JMenuItem(
3116                        localizationResources.getString("PDF..."));
3117                pdfItem.setActionCommand("SAVE_AS_PDF");
3118                pdfItem.addActionListener(this);
3119                saveSubMenu.add(pdfItem);
3120            }
3121            result.add(saveSubMenu);
3122            separator = true;
3123        }
3124
3125        if (print) {
3126            if (separator) {
3127                result.addSeparator();
3128            }
3129            JMenuItem printItem = new JMenuItem(
3130                    localizationResources.getString("Print..."));
3131            printItem.setActionCommand(PRINT_COMMAND);
3132            printItem.addActionListener(this);
3133            result.add(printItem);
3134            separator = true;
3135        }
3136
3137        if (zoom) {
3138            if (separator) {
3139                result.addSeparator();
3140            }
3141
3142            JMenu zoomInMenu = new JMenu(
3143                    localizationResources.getString("Zoom_In"));
3144
3145            this.zoomInBothMenuItem = new JMenuItem(
3146                    localizationResources.getString("All_Axes"));
3147            this.zoomInBothMenuItem.setActionCommand(ZOOM_IN_BOTH_COMMAND);
3148            this.zoomInBothMenuItem.addActionListener(this);
3149            zoomInMenu.add(this.zoomInBothMenuItem);
3150
3151            zoomInMenu.addSeparator();
3152
3153            this.zoomInDomainMenuItem = new JMenuItem(
3154                    localizationResources.getString("Domain_Axis"));
3155            this.zoomInDomainMenuItem.setActionCommand(ZOOM_IN_DOMAIN_COMMAND);
3156            this.zoomInDomainMenuItem.addActionListener(this);
3157            zoomInMenu.add(this.zoomInDomainMenuItem);
3158
3159            this.zoomInRangeMenuItem = new JMenuItem(
3160                    localizationResources.getString("Range_Axis"));
3161            this.zoomInRangeMenuItem.setActionCommand(ZOOM_IN_RANGE_COMMAND);
3162            this.zoomInRangeMenuItem.addActionListener(this);
3163            zoomInMenu.add(this.zoomInRangeMenuItem);
3164
3165            result.add(zoomInMenu);
3166
3167            JMenu zoomOutMenu = new JMenu(
3168                    localizationResources.getString("Zoom_Out"));
3169
3170            this.zoomOutBothMenuItem = new JMenuItem(
3171                    localizationResources.getString("All_Axes"));
3172            this.zoomOutBothMenuItem.setActionCommand(ZOOM_OUT_BOTH_COMMAND);
3173            this.zoomOutBothMenuItem.addActionListener(this);
3174            zoomOutMenu.add(this.zoomOutBothMenuItem);
3175
3176            zoomOutMenu.addSeparator();
3177
3178            this.zoomOutDomainMenuItem = new JMenuItem(
3179                    localizationResources.getString("Domain_Axis"));
3180            this.zoomOutDomainMenuItem.setActionCommand(
3181                    ZOOM_OUT_DOMAIN_COMMAND);
3182            this.zoomOutDomainMenuItem.addActionListener(this);
3183            zoomOutMenu.add(this.zoomOutDomainMenuItem);
3184
3185            this.zoomOutRangeMenuItem = new JMenuItem(
3186                    localizationResources.getString("Range_Axis"));
3187            this.zoomOutRangeMenuItem.setActionCommand(ZOOM_OUT_RANGE_COMMAND);
3188            this.zoomOutRangeMenuItem.addActionListener(this);
3189            zoomOutMenu.add(this.zoomOutRangeMenuItem);
3190
3191            result.add(zoomOutMenu);
3192
3193            JMenu autoRangeMenu = new JMenu(
3194                    localizationResources.getString("Auto_Range"));
3195
3196            this.zoomResetBothMenuItem = new JMenuItem(
3197                    localizationResources.getString("All_Axes"));
3198            this.zoomResetBothMenuItem.setActionCommand(
3199                    ZOOM_RESET_BOTH_COMMAND);
3200            this.zoomResetBothMenuItem.addActionListener(this);
3201            autoRangeMenu.add(this.zoomResetBothMenuItem);
3202
3203            autoRangeMenu.addSeparator();
3204            this.zoomResetDomainMenuItem = new JMenuItem(
3205                    localizationResources.getString("Domain_Axis"));
3206            this.zoomResetDomainMenuItem.setActionCommand(
3207                    ZOOM_RESET_DOMAIN_COMMAND);
3208            this.zoomResetDomainMenuItem.addActionListener(this);
3209            autoRangeMenu.add(this.zoomResetDomainMenuItem);
3210
3211            this.zoomResetRangeMenuItem = new JMenuItem(
3212                    localizationResources.getString("Range_Axis"));
3213            this.zoomResetRangeMenuItem.setActionCommand(
3214                    ZOOM_RESET_RANGE_COMMAND);
3215            this.zoomResetRangeMenuItem.addActionListener(this);
3216            autoRangeMenu.add(this.zoomResetRangeMenuItem);
3217
3218            result.addSeparator();
3219            result.add(autoRangeMenu);
3220
3221        }
3222
3223        return result;
3224
3225    }
3226
3227    /**
3228     * The idea is to modify the zooming options depending on the type of chart
3229     * being displayed by the panel.
3230     *
3231     * @param x  horizontal position of the popup.
3232     * @param y  vertical position of the popup.
3233     */
3234    protected void displayPopupMenu(int x, int y) {
3235
3236        if (this.popup == null) {
3237            return;
3238        }
3239
3240        // go through each zoom menu item and decide whether or not to
3241        // enable it...
3242        boolean isDomainZoomable = false;
3243        boolean isRangeZoomable = false;
3244        Plot plot = (this.chart != null ? this.chart.getPlot() : null);
3245        if (plot instanceof Zoomable) {
3246            Zoomable z = (Zoomable) plot;
3247            isDomainZoomable = z.isDomainZoomable();
3248            isRangeZoomable = z.isRangeZoomable();
3249        }
3250
3251        if (this.zoomInDomainMenuItem != null) {
3252            this.zoomInDomainMenuItem.setEnabled(isDomainZoomable);
3253        }
3254        if (this.zoomOutDomainMenuItem != null) {
3255            this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable);
3256        }
3257        if (this.zoomResetDomainMenuItem != null) {
3258            this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable);
3259        }
3260
3261        if (this.zoomInRangeMenuItem != null) {
3262            this.zoomInRangeMenuItem.setEnabled(isRangeZoomable);
3263        }
3264        if (this.zoomOutRangeMenuItem != null) {
3265            this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable);
3266        }
3267
3268        if (this.zoomResetRangeMenuItem != null) {
3269            this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable);
3270        }
3271
3272        if (this.zoomInBothMenuItem != null) {
3273            this.zoomInBothMenuItem.setEnabled(isDomainZoomable
3274                    && isRangeZoomable);
3275        }
3276        if (this.zoomOutBothMenuItem != null) {
3277            this.zoomOutBothMenuItem.setEnabled(isDomainZoomable
3278                    && isRangeZoomable);
3279        }
3280        if (this.zoomResetBothMenuItem != null) {
3281            this.zoomResetBothMenuItem.setEnabled(isDomainZoomable
3282                    && isRangeZoomable);
3283        }
3284
3285        this.popup.show(this, x, y);
3286
3287    }
3288
3289    /**
3290     * Updates the UI for a LookAndFeel change.
3291     */
3292    @Override
3293    public void updateUI() {
3294        // here we need to update the UI for the popup menu, if the panel
3295        // has one...
3296        if (this.popup != null) {
3297            SwingUtilities.updateComponentTreeUI(this.popup);
3298        }
3299        super.updateUI();
3300    }
3301
3302    /**
3303     * Provides serialization support.
3304     *
3305     * @param stream  the output stream.
3306     *
3307     * @throws IOException  if there is an I/O error.
3308     */
3309    private void writeObject(ObjectOutputStream stream) throws IOException {
3310        stream.defaultWriteObject();
3311        SerialUtilities.writePaint(this.zoomFillPaint, stream);
3312        SerialUtilities.writePaint(this.zoomOutlinePaint, stream);
3313    }
3314
3315    /**
3316     * Provides serialization support.
3317     *
3318     * @param stream  the input stream.
3319     *
3320     * @throws IOException  if there is an I/O error.
3321     * @throws ClassNotFoundException  if there is a classpath problem.
3322     */
3323    private void readObject(ObjectInputStream stream)
3324        throws IOException, ClassNotFoundException {
3325        stream.defaultReadObject();
3326        this.zoomFillPaint = SerialUtilities.readPaint(stream);
3327        this.zoomOutlinePaint = SerialUtilities.readPaint(stream);
3328
3329        // we create a new but empty chartMouseListeners list
3330        this.chartMouseListeners = new EventListenerList();
3331
3332        // register as a listener with sub-components...
3333        if (this.chart != null) {
3334            this.chart.addChangeListener(this);
3335        }
3336
3337    }
3338
3339}