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