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