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