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