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