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