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