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