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