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