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