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