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