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