001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2014, 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-2014, 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 enabled 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 } else { 1628 this.chart.draw(bufferG2, bufferArea, this.anchor, 1629 this.info); 1630 } 1631 1632 } 1633 1634 // zap the buffer onto the panel... 1635 g2.drawImage(this.chartBuffer, insets.left, insets.top, this); 1636 1637 } else { // redrawing the chart every time... 1638 AffineTransform saved = g2.getTransform(); 1639 g2.translate(insets.left, insets.top); 1640 if (scale) { 1641 AffineTransform st = AffineTransform.getScaleInstance( 1642 this.scaleX, this.scaleY); 1643 g2.transform(st); 1644 } 1645 this.chart.draw(g2, chartArea, this.anchor, this.info); 1646 g2.setTransform(saved); 1647 1648 } 1649 1650 Iterator iterator = this.overlays.iterator(); 1651 while (iterator.hasNext()) { 1652 Overlay overlay = (Overlay) iterator.next(); 1653 overlay.paintOverlay(g2, this); 1654 } 1655 1656 // redraw the zoom rectangle (if present) - if useBuffer is false, 1657 // we use XOR so we can XOR the rectangle away again without redrawing 1658 // the chart 1659 drawZoomRectangle(g2, !this.useBuffer); 1660 1661 g2.dispose(); 1662 1663 this.anchor = null; 1664 this.verticalTraceLine = null; 1665 this.horizontalTraceLine = null; 1666 } 1667 1668 /** 1669 * Receives notification of changes to the chart, and redraws the chart. 1670 * 1671 * @param event details of the chart change event. 1672 */ 1673 @Override 1674 public void chartChanged(ChartChangeEvent event) { 1675 this.refreshBuffer = true; 1676 Plot plot = this.chart.getPlot(); 1677 if (plot instanceof Zoomable) { 1678 Zoomable z = (Zoomable) plot; 1679 this.orientation = z.getOrientation(); 1680 } 1681 repaint(); 1682 } 1683 1684 /** 1685 * Receives notification of a chart progress event. 1686 * 1687 * @param event the event. 1688 */ 1689 @Override 1690 public void chartProgress(ChartProgressEvent event) { 1691 // does nothing - override if necessary 1692 } 1693 1694 /** 1695 * Handles action events generated by the popup menu. 1696 * 1697 * @param event the event. 1698 */ 1699 @Override 1700 public void actionPerformed(ActionEvent event) { 1701 1702 String command = event.getActionCommand(); 1703 1704 // many of the zoom methods need a screen location - all we have is 1705 // the zoomPoint, but it might be null. Here we grab the x and y 1706 // coordinates, or use defaults... 1707 double screenX = -1.0; 1708 double screenY = -1.0; 1709 if (this.zoomPoint != null) { 1710 screenX = this.zoomPoint.getX(); 1711 screenY = this.zoomPoint.getY(); 1712 } 1713 1714 if (command.equals(PROPERTIES_COMMAND)) { 1715 doEditChartProperties(); 1716 } 1717 else if (command.equals(COPY_COMMAND)) { 1718 doCopy(); 1719 } 1720 else if (command.equals(SAVE_AS_PNG_COMMAND)) { 1721 try { 1722 doSaveAs(); 1723 } 1724 catch (IOException e) { 1725 JOptionPane.showMessageDialog(this, "I/O error occurred.", 1726 "Save As PNG", JOptionPane.WARNING_MESSAGE); 1727 } 1728 } 1729 else if (command.equals(SAVE_AS_SVG_COMMAND)) { 1730 try { 1731 saveAsSVG(null); 1732 } catch (IOException e) { 1733 JOptionPane.showMessageDialog(this, "I/O error occurred.", 1734 "Save As SVG", JOptionPane.WARNING_MESSAGE); 1735 } 1736 } 1737 else if (command.equals(SAVE_AS_PDF_COMMAND)) { 1738 saveAsPDF(null); 1739 } 1740 else if (command.equals(PRINT_COMMAND)) { 1741 createChartPrintJob(); 1742 } 1743 else if (command.equals(ZOOM_IN_BOTH_COMMAND)) { 1744 zoomInBoth(screenX, screenY); 1745 } 1746 else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) { 1747 zoomInDomain(screenX, screenY); 1748 } 1749 else if (command.equals(ZOOM_IN_RANGE_COMMAND)) { 1750 zoomInRange(screenX, screenY); 1751 } 1752 else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) { 1753 zoomOutBoth(screenX, screenY); 1754 } 1755 else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) { 1756 zoomOutDomain(screenX, screenY); 1757 } 1758 else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) { 1759 zoomOutRange(screenX, screenY); 1760 } 1761 else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) { 1762 restoreAutoBounds(); 1763 } 1764 else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) { 1765 restoreAutoDomainBounds(); 1766 } 1767 else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) { 1768 restoreAutoRangeBounds(); 1769 } 1770 1771 } 1772 1773 /** 1774 * Handles a 'mouse entered' event. This method changes the tooltip delays 1775 * of ToolTipManager.sharedInstance() to the possibly different values set 1776 * for this chart panel. 1777 * 1778 * @param e the mouse event. 1779 */ 1780 @Override 1781 public void mouseEntered(MouseEvent e) { 1782 if (!this.ownToolTipDelaysActive) { 1783 ToolTipManager ttm = ToolTipManager.sharedInstance(); 1784 1785 this.originalToolTipInitialDelay = ttm.getInitialDelay(); 1786 ttm.setInitialDelay(this.ownToolTipInitialDelay); 1787 1788 this.originalToolTipReshowDelay = ttm.getReshowDelay(); 1789 ttm.setReshowDelay(this.ownToolTipReshowDelay); 1790 1791 this.originalToolTipDismissDelay = ttm.getDismissDelay(); 1792 ttm.setDismissDelay(this.ownToolTipDismissDelay); 1793 1794 this.ownToolTipDelaysActive = true; 1795 } 1796 } 1797 1798 /** 1799 * Handles a 'mouse exited' event. This method resets the tooltip delays of 1800 * ToolTipManager.sharedInstance() to their 1801 * original values in effect before mouseEntered() 1802 * 1803 * @param e the mouse event. 1804 */ 1805 @Override 1806 public void mouseExited(MouseEvent e) { 1807 if (this.ownToolTipDelaysActive) { 1808 // restore original tooltip dealys 1809 ToolTipManager ttm = ToolTipManager.sharedInstance(); 1810 ttm.setInitialDelay(this.originalToolTipInitialDelay); 1811 ttm.setReshowDelay(this.originalToolTipReshowDelay); 1812 ttm.setDismissDelay(this.originalToolTipDismissDelay); 1813 this.ownToolTipDelaysActive = false; 1814 } 1815 } 1816 1817 /** 1818 * Handles a 'mouse pressed' event. 1819 * <P> 1820 * This event is the popup trigger on Unix/Linux. For Windows, the popup 1821 * trigger is the 'mouse released' event. 1822 * 1823 * @param e The mouse event. 1824 */ 1825 @Override 1826 public void mousePressed(MouseEvent e) { 1827 if (this.chart == null) { 1828 return; 1829 } 1830 Plot plot = this.chart.getPlot(); 1831 int mods = e.getModifiers(); 1832 if ((mods & this.panMask) == this.panMask) { 1833 // can we pan this plot? 1834 if (plot instanceof Pannable) { 1835 Pannable pannable = (Pannable) plot; 1836 if (pannable.isDomainPannable() || pannable.isRangePannable()) { 1837 Rectangle2D screenDataArea = getScreenDataArea(e.getX(), 1838 e.getY()); 1839 if (screenDataArea != null && screenDataArea.contains( 1840 e.getPoint())) { 1841 this.panW = screenDataArea.getWidth(); 1842 this.panH = screenDataArea.getHeight(); 1843 this.panLast = e.getPoint(); 1844 setCursor(Cursor.getPredefinedCursor( 1845 Cursor.MOVE_CURSOR)); 1846 } 1847 } 1848 // the actual panning occurs later in the mouseDragged() 1849 // method 1850 } 1851 } 1852 else if (this.zoomRectangle == null) { 1853 Rectangle2D screenDataArea = getScreenDataArea(e.getX(), e.getY()); 1854 if (screenDataArea != null) { 1855 this.zoomPoint = getPointInRectangle(e.getX(), e.getY(), 1856 screenDataArea); 1857 } 1858 else { 1859 this.zoomPoint = null; 1860 } 1861 if (e.isPopupTrigger()) { 1862 if (this.popup != null) { 1863 displayPopupMenu(e.getX(), e.getY()); 1864 } 1865 } 1866 } 1867 } 1868 1869 /** 1870 * Returns a point based on (x, y) but constrained to be within the bounds 1871 * of the given rectangle. This method could be moved to JCommon. 1872 * 1873 * @param x the x-coordinate. 1874 * @param y the y-coordinate. 1875 * @param area the rectangle (<code>null</code> not permitted). 1876 * 1877 * @return A point within the rectangle. 1878 */ 1879 private Point2D getPointInRectangle(int x, int y, Rectangle2D area) { 1880 double xx = Math.max(area.getMinX(), Math.min(x, area.getMaxX())); 1881 double yy = Math.max(area.getMinY(), Math.min(y, area.getMaxY())); 1882 return new Point2D.Double(xx, yy); 1883 } 1884 1885 /** 1886 * Handles a 'mouse dragged' event. 1887 * 1888 * @param e the mouse event. 1889 */ 1890 @Override 1891 public void mouseDragged(MouseEvent e) { 1892 1893 // if the popup menu has already been triggered, then ignore dragging... 1894 if (this.popup != null && this.popup.isShowing()) { 1895 return; 1896 } 1897 1898 // handle panning if we have a start point 1899 if (this.panLast != null) { 1900 double dx = e.getX() - this.panLast.getX(); 1901 double dy = e.getY() - this.panLast.getY(); 1902 if (dx == 0.0 && dy == 0.0) { 1903 return; 1904 } 1905 double wPercent = -dx / this.panW; 1906 double hPercent = dy / this.panH; 1907 boolean old = this.chart.getPlot().isNotify(); 1908 this.chart.getPlot().setNotify(false); 1909 Pannable p = (Pannable) this.chart.getPlot(); 1910 if (p.getOrientation() == PlotOrientation.VERTICAL) { 1911 p.panDomainAxes(wPercent, this.info.getPlotInfo(), 1912 this.panLast); 1913 p.panRangeAxes(hPercent, this.info.getPlotInfo(), 1914 this.panLast); 1915 } 1916 else { 1917 p.panDomainAxes(hPercent, this.info.getPlotInfo(), 1918 this.panLast); 1919 p.panRangeAxes(wPercent, this.info.getPlotInfo(), 1920 this.panLast); 1921 } 1922 this.panLast = e.getPoint(); 1923 this.chart.getPlot().setNotify(old); 1924 return; 1925 } 1926 1927 // if no initial zoom point was set, ignore dragging... 1928 if (this.zoomPoint == null) { 1929 return; 1930 } 1931 Graphics2D g2 = (Graphics2D) getGraphics(); 1932 1933 // erase the previous zoom rectangle (if any). We only need to do 1934 // this is we are using XOR mode, which we do when we're not using 1935 // the buffer (if there is a buffer, then at the end of this method we 1936 // just trigger a repaint) 1937 if (!this.useBuffer) { 1938 drawZoomRectangle(g2, true); 1939 } 1940 1941 boolean hZoom, vZoom; 1942 if (this.orientation == PlotOrientation.HORIZONTAL) { 1943 hZoom = this.rangeZoomable; 1944 vZoom = this.domainZoomable; 1945 } 1946 else { 1947 hZoom = this.domainZoomable; 1948 vZoom = this.rangeZoomable; 1949 } 1950 Rectangle2D scaledDataArea = getScreenDataArea( 1951 (int) this.zoomPoint.getX(), (int) this.zoomPoint.getY()); 1952 if (hZoom && vZoom) { 1953 // selected rectangle shouldn't extend outside the data area... 1954 double xmax = Math.min(e.getX(), scaledDataArea.getMaxX()); 1955 double ymax = Math.min(e.getY(), scaledDataArea.getMaxY()); 1956 this.zoomRectangle = new Rectangle2D.Double( 1957 this.zoomPoint.getX(), this.zoomPoint.getY(), 1958 xmax - this.zoomPoint.getX(), ymax - this.zoomPoint.getY()); 1959 } 1960 else if (hZoom) { 1961 double xmax = Math.min(e.getX(), scaledDataArea.getMaxX()); 1962 this.zoomRectangle = new Rectangle2D.Double( 1963 this.zoomPoint.getX(), scaledDataArea.getMinY(), 1964 xmax - this.zoomPoint.getX(), scaledDataArea.getHeight()); 1965 } 1966 else if (vZoom) { 1967 double ymax = Math.min(e.getY(), scaledDataArea.getMaxY()); 1968 this.zoomRectangle = new Rectangle2D.Double( 1969 scaledDataArea.getMinX(), this.zoomPoint.getY(), 1970 scaledDataArea.getWidth(), ymax - this.zoomPoint.getY()); 1971 } 1972 1973 // Draw the new zoom rectangle... 1974 if (this.useBuffer) { 1975 repaint(); 1976 } 1977 else { 1978 // with no buffer, we use XOR to draw the rectangle "over" the 1979 // chart... 1980 drawZoomRectangle(g2, true); 1981 } 1982 g2.dispose(); 1983 1984 } 1985 1986 /** 1987 * Handles a 'mouse released' event. On Windows, we need to check if this 1988 * is a popup trigger, but only if we haven't already been tracking a zoom 1989 * rectangle. 1990 * 1991 * @param e information about the event. 1992 */ 1993 @Override 1994 public void mouseReleased(MouseEvent e) { 1995 1996 // if we've been panning, we need to reset now that the mouse is 1997 // released... 1998 if (this.panLast != null) { 1999 this.panLast = null; 2000 setCursor(Cursor.getDefaultCursor()); 2001 } 2002 2003 else if (this.zoomRectangle != null) { 2004 boolean hZoom, vZoom; 2005 if (this.orientation == PlotOrientation.HORIZONTAL) { 2006 hZoom = this.rangeZoomable; 2007 vZoom = this.domainZoomable; 2008 } 2009 else { 2010 hZoom = this.domainZoomable; 2011 vZoom = this.rangeZoomable; 2012 } 2013 2014 boolean zoomTrigger1 = hZoom && Math.abs(e.getX() 2015 - this.zoomPoint.getX()) >= this.zoomTriggerDistance; 2016 boolean zoomTrigger2 = vZoom && Math.abs(e.getY() 2017 - this.zoomPoint.getY()) >= this.zoomTriggerDistance; 2018 if (zoomTrigger1 || zoomTrigger2) { 2019 if ((hZoom && (e.getX() < this.zoomPoint.getX())) 2020 || (vZoom && (e.getY() < this.zoomPoint.getY()))) { 2021 restoreAutoBounds(); 2022 } 2023 else { 2024 double x, y, w, h; 2025 Rectangle2D screenDataArea = getScreenDataArea( 2026 (int) this.zoomPoint.getX(), 2027 (int) this.zoomPoint.getY()); 2028 double maxX = screenDataArea.getMaxX(); 2029 double maxY = screenDataArea.getMaxY(); 2030 // for mouseReleased event, (horizontalZoom || verticalZoom) 2031 // will be true, so we can just test for either being false; 2032 // otherwise both are true 2033 if (!vZoom) { 2034 x = this.zoomPoint.getX(); 2035 y = screenDataArea.getMinY(); 2036 w = Math.min(this.zoomRectangle.getWidth(), 2037 maxX - this.zoomPoint.getX()); 2038 h = screenDataArea.getHeight(); 2039 } 2040 else if (!hZoom) { 2041 x = screenDataArea.getMinX(); 2042 y = this.zoomPoint.getY(); 2043 w = screenDataArea.getWidth(); 2044 h = Math.min(this.zoomRectangle.getHeight(), 2045 maxY - this.zoomPoint.getY()); 2046 } 2047 else { 2048 x = this.zoomPoint.getX(); 2049 y = this.zoomPoint.getY(); 2050 w = Math.min(this.zoomRectangle.getWidth(), 2051 maxX - this.zoomPoint.getX()); 2052 h = Math.min(this.zoomRectangle.getHeight(), 2053 maxY - this.zoomPoint.getY()); 2054 } 2055 Rectangle2D zoomArea = new Rectangle2D.Double(x, y, w, h); 2056 zoom(zoomArea); 2057 } 2058 this.zoomPoint = null; 2059 this.zoomRectangle = null; 2060 } 2061 else { 2062 // erase the zoom rectangle 2063 Graphics2D g2 = (Graphics2D) getGraphics(); 2064 if (this.useBuffer) { 2065 repaint(); 2066 } 2067 else { 2068 drawZoomRectangle(g2, true); 2069 } 2070 g2.dispose(); 2071 this.zoomPoint = null; 2072 this.zoomRectangle = null; 2073 } 2074 2075 } 2076 2077 else if (e.isPopupTrigger()) { 2078 if (this.popup != null) { 2079 displayPopupMenu(e.getX(), e.getY()); 2080 } 2081 } 2082 2083 } 2084 2085 /** 2086 * Receives notification of mouse clicks on the panel. These are 2087 * translated and passed on to any registered {@link ChartMouseListener}s. 2088 * 2089 * @param event Information about the mouse event. 2090 */ 2091 @Override 2092 public void mouseClicked(MouseEvent event) { 2093 2094 Insets insets = getInsets(); 2095 int x = (int) ((event.getX() - insets.left) / this.scaleX); 2096 int y = (int) ((event.getY() - insets.top) / this.scaleY); 2097 2098 this.anchor = new Point2D.Double(x, y); 2099 if (this.chart == null) { 2100 return; 2101 } 2102 this.chart.setNotify(true); // force a redraw 2103 // new entity code... 2104 Object[] listeners = this.chartMouseListeners.getListeners( 2105 ChartMouseListener.class); 2106 if (listeners.length == 0) { 2107 return; 2108 } 2109 2110 ChartEntity entity = null; 2111 if (this.info != null) { 2112 EntityCollection entities = this.info.getEntityCollection(); 2113 if (entities != null) { 2114 entity = entities.getEntity(x, y); 2115 } 2116 } 2117 ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(), event, 2118 entity); 2119 for (int i = listeners.length - 1; i >= 0; i -= 1) { 2120 ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent); 2121 } 2122 2123 } 2124 2125 /** 2126 * Implementation of the MouseMotionListener's method. 2127 * 2128 * @param e the event. 2129 */ 2130 @Override 2131 public void mouseMoved(MouseEvent e) { 2132 Graphics2D g2 = (Graphics2D) getGraphics(); 2133 if (this.horizontalAxisTrace) { 2134 drawHorizontalAxisTrace(g2, e.getX()); 2135 } 2136 if (this.verticalAxisTrace) { 2137 drawVerticalAxisTrace(g2, e.getY()); 2138 } 2139 g2.dispose(); 2140 2141 Object[] listeners = this.chartMouseListeners.getListeners( 2142 ChartMouseListener.class); 2143 if (listeners.length == 0) { 2144 return; 2145 } 2146 Insets insets = getInsets(); 2147 int x = (int) ((e.getX() - insets.left) / this.scaleX); 2148 int y = (int) ((e.getY() - insets.top) / this.scaleY); 2149 2150 ChartEntity entity = null; 2151 if (this.info != null) { 2152 EntityCollection entities = this.info.getEntityCollection(); 2153 if (entities != null) { 2154 entity = entities.getEntity(x, y); 2155 } 2156 } 2157 2158 // we can only generate events if the panel's chart is not null 2159 // (see bug report 1556951) 2160 if (this.chart != null) { 2161 ChartMouseEvent event = new ChartMouseEvent(getChart(), e, entity); 2162 for (int i = listeners.length - 1; i >= 0; i -= 1) { 2163 ((ChartMouseListener) listeners[i]).chartMouseMoved(event); 2164 } 2165 } 2166 2167 } 2168 2169 /** 2170 * Zooms in on an anchor point (specified in screen coordinate space). 2171 * 2172 * @param x the x value (in screen coordinates). 2173 * @param y the y value (in screen coordinates). 2174 */ 2175 public void zoomInBoth(double x, double y) { 2176 Plot plot = this.chart.getPlot(); 2177 if (plot == null) { 2178 return; 2179 } 2180 // here we tweak the notify flag on the plot so that only 2181 // one notification happens even though we update multiple 2182 // axes... 2183 boolean savedNotify = plot.isNotify(); 2184 plot.setNotify(false); 2185 zoomInDomain(x, y); 2186 zoomInRange(x, y); 2187 plot.setNotify(savedNotify); 2188 } 2189 2190 /** 2191 * Decreases the length of the domain axis, centered about the given 2192 * coordinate on the screen. The length of the domain axis is reduced 2193 * by the value of {@link #getZoomInFactor()}. 2194 * 2195 * @param x the x coordinate (in screen coordinates). 2196 * @param y the y-coordinate (in screen coordinates). 2197 */ 2198 public void zoomInDomain(double x, double y) { 2199 Plot plot = this.chart.getPlot(); 2200 if (plot instanceof Zoomable) { 2201 // here we tweak the notify flag on the plot so that only 2202 // one notification happens even though we update multiple 2203 // axes... 2204 boolean savedNotify = plot.isNotify(); 2205 plot.setNotify(false); 2206 Zoomable z = (Zoomable) plot; 2207 z.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(), 2208 translateScreenToJava2D(new Point((int) x, (int) y)), 2209 this.zoomAroundAnchor); 2210 plot.setNotify(savedNotify); 2211 } 2212 } 2213 2214 /** 2215 * Decreases the length of the range axis, centered about the given 2216 * coordinate on the screen. The length of the range axis is reduced by 2217 * the value of {@link #getZoomInFactor()}. 2218 * 2219 * @param x the x-coordinate (in screen coordinates). 2220 * @param y the y coordinate (in screen coordinates). 2221 */ 2222 public void zoomInRange(double x, double y) { 2223 Plot plot = this.chart.getPlot(); 2224 if (plot instanceof Zoomable) { 2225 // here we tweak the notify flag on the plot so that only 2226 // one notification happens even though we update multiple 2227 // axes... 2228 boolean savedNotify = plot.isNotify(); 2229 plot.setNotify(false); 2230 Zoomable z = (Zoomable) plot; 2231 z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(), 2232 translateScreenToJava2D(new Point((int) x, (int) y)), 2233 this.zoomAroundAnchor); 2234 plot.setNotify(savedNotify); 2235 } 2236 } 2237 2238 /** 2239 * Zooms out on an anchor point (specified in screen coordinate space). 2240 * 2241 * @param x the x value (in screen coordinates). 2242 * @param y the y value (in screen coordinates). 2243 */ 2244 public void zoomOutBoth(double x, double y) { 2245 Plot plot = this.chart.getPlot(); 2246 if (plot == null) { 2247 return; 2248 } 2249 // here we tweak the notify flag on the plot so that only 2250 // one notification happens even though we update multiple 2251 // axes... 2252 boolean savedNotify = plot.isNotify(); 2253 plot.setNotify(false); 2254 zoomOutDomain(x, y); 2255 zoomOutRange(x, y); 2256 plot.setNotify(savedNotify); 2257 } 2258 2259 /** 2260 * Increases the length of the domain axis, centered about the given 2261 * coordinate on the screen. The length of the domain axis is increased 2262 * by the value of {@link #getZoomOutFactor()}. 2263 * 2264 * @param x the x coordinate (in screen coordinates). 2265 * @param y the y-coordinate (in screen coordinates). 2266 */ 2267 public void zoomOutDomain(double x, double y) { 2268 Plot plot = this.chart.getPlot(); 2269 if (plot instanceof Zoomable) { 2270 // here we tweak the notify flag on the plot so that only 2271 // one notification happens even though we update multiple 2272 // axes... 2273 boolean savedNotify = plot.isNotify(); 2274 plot.setNotify(false); 2275 Zoomable z = (Zoomable) plot; 2276 z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(), 2277 translateScreenToJava2D(new Point((int) x, (int) y)), 2278 this.zoomAroundAnchor); 2279 plot.setNotify(savedNotify); 2280 } 2281 } 2282 2283 /** 2284 * Increases the length the range axis, centered about the given 2285 * coordinate on the screen. The length of the range axis is increased 2286 * by the value of {@link #getZoomOutFactor()}. 2287 * 2288 * @param x the x coordinate (in screen coordinates). 2289 * @param y the y-coordinate (in screen coordinates). 2290 */ 2291 public void zoomOutRange(double x, double y) { 2292 Plot plot = this.chart.getPlot(); 2293 if (plot instanceof Zoomable) { 2294 // here we tweak the notify flag on the plot so that only 2295 // one notification happens even though we update multiple 2296 // axes... 2297 boolean savedNotify = plot.isNotify(); 2298 plot.setNotify(false); 2299 Zoomable z = (Zoomable) plot; 2300 z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(), 2301 translateScreenToJava2D(new Point((int) x, (int) y)), 2302 this.zoomAroundAnchor); 2303 plot.setNotify(savedNotify); 2304 } 2305 } 2306 2307 /** 2308 * Zooms in on a selected region. 2309 * 2310 * @param selection the selected region. 2311 */ 2312 public void zoom(Rectangle2D selection) { 2313 2314 // get the origin of the zoom selection in the Java2D space used for 2315 // drawing the chart (that is, before any scaling to fit the panel) 2316 Point2D selectOrigin = translateScreenToJava2D(new Point( 2317 (int) Math.ceil(selection.getX()), 2318 (int) Math.ceil(selection.getY()))); 2319 PlotRenderingInfo plotInfo = this.info.getPlotInfo(); 2320 Rectangle2D scaledDataArea = getScreenDataArea( 2321 (int) selection.getCenterX(), (int) selection.getCenterY()); 2322 if ((selection.getHeight() > 0) && (selection.getWidth() > 0)) { 2323 2324 double hLower = (selection.getMinX() - scaledDataArea.getMinX()) 2325 / scaledDataArea.getWidth(); 2326 double hUpper = (selection.getMaxX() - scaledDataArea.getMinX()) 2327 / scaledDataArea.getWidth(); 2328 double vLower = (scaledDataArea.getMaxY() - selection.getMaxY()) 2329 / scaledDataArea.getHeight(); 2330 double vUpper = (scaledDataArea.getMaxY() - selection.getMinY()) 2331 / scaledDataArea.getHeight(); 2332 2333 Plot p = this.chart.getPlot(); 2334 if (p instanceof Zoomable) { 2335 // here we tweak the notify flag on the plot so that only 2336 // one notification happens even though we update multiple 2337 // axes... 2338 boolean savedNotify = p.isNotify(); 2339 p.setNotify(false); 2340 Zoomable z = (Zoomable) p; 2341 if (z.getOrientation() == PlotOrientation.HORIZONTAL) { 2342 z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin); 2343 z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin); 2344 } 2345 else { 2346 z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin); 2347 z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin); 2348 } 2349 p.setNotify(savedNotify); 2350 } 2351 2352 } 2353 2354 } 2355 2356 /** 2357 * Restores the auto-range calculation on both axes. 2358 */ 2359 public void restoreAutoBounds() { 2360 Plot plot = this.chart.getPlot(); 2361 if (plot == null) { 2362 return; 2363 } 2364 // here we tweak the notify flag on the plot so that only 2365 // one notification happens even though we update multiple 2366 // axes... 2367 boolean savedNotify = plot.isNotify(); 2368 plot.setNotify(false); 2369 restoreAutoDomainBounds(); 2370 restoreAutoRangeBounds(); 2371 plot.setNotify(savedNotify); 2372 } 2373 2374 /** 2375 * Restores the auto-range calculation on the domain axis. 2376 */ 2377 public void restoreAutoDomainBounds() { 2378 Plot plot = this.chart.getPlot(); 2379 if (plot instanceof Zoomable) { 2380 Zoomable z = (Zoomable) plot; 2381 // here we tweak the notify flag on the plot so that only 2382 // one notification happens even though we update multiple 2383 // axes... 2384 boolean savedNotify = plot.isNotify(); 2385 plot.setNotify(false); 2386 // we need to guard against this.zoomPoint being null 2387 Point2D zp = (this.zoomPoint != null 2388 ? this.zoomPoint : new Point()); 2389 z.zoomDomainAxes(0.0, this.info.getPlotInfo(), zp); 2390 plot.setNotify(savedNotify); 2391 } 2392 } 2393 2394 /** 2395 * Restores the auto-range calculation on the range axis. 2396 */ 2397 public void restoreAutoRangeBounds() { 2398 Plot plot = this.chart.getPlot(); 2399 if (plot instanceof Zoomable) { 2400 Zoomable z = (Zoomable) plot; 2401 // here we tweak the notify flag on the plot so that only 2402 // one notification happens even though we update multiple 2403 // axes... 2404 boolean savedNotify = plot.isNotify(); 2405 plot.setNotify(false); 2406 // we need to guard against this.zoomPoint being null 2407 Point2D zp = (this.zoomPoint != null 2408 ? this.zoomPoint : new Point()); 2409 z.zoomRangeAxes(0.0, this.info.getPlotInfo(), zp); 2410 plot.setNotify(savedNotify); 2411 } 2412 } 2413 2414 /** 2415 * Returns the data area for the chart (the area inside the axes) with the 2416 * current scaling applied (that is, the area as it appears on screen). 2417 * 2418 * @return The scaled data area. 2419 */ 2420 public Rectangle2D getScreenDataArea() { 2421 Rectangle2D dataArea = this.info.getPlotInfo().getDataArea(); 2422 Insets insets = getInsets(); 2423 double x = dataArea.getX() * this.scaleX + insets.left; 2424 double y = dataArea.getY() * this.scaleY + insets.top; 2425 double w = dataArea.getWidth() * this.scaleX; 2426 double h = dataArea.getHeight() * this.scaleY; 2427 return new Rectangle2D.Double(x, y, w, h); 2428 } 2429 2430 /** 2431 * Returns the data area (the area inside the axes) for the plot or subplot, 2432 * with the current scaling applied. 2433 * 2434 * @param x the x-coordinate (for subplot selection). 2435 * @param y the y-coordinate (for subplot selection). 2436 * 2437 * @return The scaled data area. 2438 */ 2439 public Rectangle2D getScreenDataArea(int x, int y) { 2440 PlotRenderingInfo plotInfo = this.info.getPlotInfo(); 2441 Rectangle2D result; 2442 if (plotInfo.getSubplotCount() == 0) { 2443 result = getScreenDataArea(); 2444 } 2445 else { 2446 // get the origin of the zoom selection in the Java2D space used for 2447 // drawing the chart (that is, before any scaling to fit the panel) 2448 Point2D selectOrigin = translateScreenToJava2D(new Point(x, y)); 2449 int subplotIndex = plotInfo.getSubplotIndex(selectOrigin); 2450 if (subplotIndex == -1) { 2451 return null; 2452 } 2453 result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea()); 2454 } 2455 return result; 2456 } 2457 2458 /** 2459 * Returns the initial tooltip delay value used inside this chart panel. 2460 * 2461 * @return An integer representing the initial delay value, in milliseconds. 2462 * 2463 * @see javax.swing.ToolTipManager#getInitialDelay() 2464 */ 2465 public int getInitialDelay() { 2466 return this.ownToolTipInitialDelay; 2467 } 2468 2469 /** 2470 * Returns the reshow tooltip delay value used inside this chart panel. 2471 * 2472 * @return An integer representing the reshow delay value, in milliseconds. 2473 * 2474 * @see javax.swing.ToolTipManager#getReshowDelay() 2475 */ 2476 public int getReshowDelay() { 2477 return this.ownToolTipReshowDelay; 2478 } 2479 2480 /** 2481 * Returns the dismissal tooltip delay value used inside this chart panel. 2482 * 2483 * @return An integer representing the dismissal delay value, in 2484 * milliseconds. 2485 * 2486 * @see javax.swing.ToolTipManager#getDismissDelay() 2487 */ 2488 public int getDismissDelay() { 2489 return this.ownToolTipDismissDelay; 2490 } 2491 2492 /** 2493 * Specifies the initial delay value for this chart panel. 2494 * 2495 * @param delay the number of milliseconds to delay (after the cursor has 2496 * paused) before displaying. 2497 * 2498 * @see javax.swing.ToolTipManager#setInitialDelay(int) 2499 */ 2500 public void setInitialDelay(int delay) { 2501 this.ownToolTipInitialDelay = delay; 2502 } 2503 2504 /** 2505 * Specifies the amount of time before the user has to wait initialDelay 2506 * milliseconds before a tooltip will be shown. 2507 * 2508 * @param delay time in milliseconds 2509 * 2510 * @see javax.swing.ToolTipManager#setReshowDelay(int) 2511 */ 2512 public void setReshowDelay(int delay) { 2513 this.ownToolTipReshowDelay = delay; 2514 } 2515 2516 /** 2517 * Specifies the dismissal delay value for this chart panel. 2518 * 2519 * @param delay the number of milliseconds to delay before taking away the 2520 * tooltip 2521 * 2522 * @see javax.swing.ToolTipManager#setDismissDelay(int) 2523 */ 2524 public void setDismissDelay(int delay) { 2525 this.ownToolTipDismissDelay = delay; 2526 } 2527 2528 /** 2529 * Returns the zoom in factor. 2530 * 2531 * @return The zoom in factor. 2532 * 2533 * @see #setZoomInFactor(double) 2534 */ 2535 public double getZoomInFactor() { 2536 return this.zoomInFactor; 2537 } 2538 2539 /** 2540 * Sets the zoom in factor. 2541 * 2542 * @param factor the factor. 2543 * 2544 * @see #getZoomInFactor() 2545 */ 2546 public void setZoomInFactor(double factor) { 2547 this.zoomInFactor = factor; 2548 } 2549 2550 /** 2551 * Returns the zoom out factor. 2552 * 2553 * @return The zoom out factor. 2554 * 2555 * @see #setZoomOutFactor(double) 2556 */ 2557 public double getZoomOutFactor() { 2558 return this.zoomOutFactor; 2559 } 2560 2561 /** 2562 * Sets the zoom out factor. 2563 * 2564 * @param factor the factor. 2565 * 2566 * @see #getZoomOutFactor() 2567 */ 2568 public void setZoomOutFactor(double factor) { 2569 this.zoomOutFactor = factor; 2570 } 2571 2572 /** 2573 * Draws zoom rectangle (if present). 2574 * The drawing is performed in XOR mode, therefore 2575 * when this method is called twice in a row, 2576 * the second call will completely restore the state 2577 * of the canvas. 2578 * 2579 * @param g2 the graphics device. 2580 * @param xor use XOR for drawing? 2581 */ 2582 private void drawZoomRectangle(Graphics2D g2, boolean xor) { 2583 if (this.zoomRectangle != null) { 2584 if (xor) { 2585 // Set XOR mode to draw the zoom rectangle 2586 g2.setXORMode(Color.gray); 2587 } 2588 if (this.fillZoomRectangle) { 2589 g2.setPaint(this.zoomFillPaint); 2590 g2.fill(this.zoomRectangle); 2591 } 2592 else { 2593 g2.setPaint(this.zoomOutlinePaint); 2594 g2.draw(this.zoomRectangle); 2595 } 2596 if (xor) { 2597 // Reset to the default 'overwrite' mode 2598 g2.setPaintMode(); 2599 } 2600 } 2601 } 2602 2603 /** 2604 * Draws a vertical line used to trace the mouse position to the horizontal 2605 * axis. 2606 * 2607 * @param g2 the graphics device. 2608 * @param x the x-coordinate of the trace line. 2609 */ 2610 private void drawHorizontalAxisTrace(Graphics2D g2, int x) { 2611 2612 Rectangle2D dataArea = getScreenDataArea(); 2613 2614 g2.setXORMode(Color.orange); 2615 if (((int) dataArea.getMinX() < x) && (x < (int) dataArea.getMaxX())) { 2616 2617 if (this.verticalTraceLine != null) { 2618 g2.draw(this.verticalTraceLine); 2619 this.verticalTraceLine.setLine(x, (int) dataArea.getMinY(), x, 2620 (int) dataArea.getMaxY()); 2621 } 2622 else { 2623 this.verticalTraceLine = new Line2D.Float(x, 2624 (int) dataArea.getMinY(), x, (int) dataArea.getMaxY()); 2625 } 2626 g2.draw(this.verticalTraceLine); 2627 } 2628 2629 // Reset to the default 'overwrite' mode 2630 g2.setPaintMode(); 2631 } 2632 2633 /** 2634 * Draws a horizontal line used to trace the mouse position to the vertical 2635 * axis. 2636 * 2637 * @param g2 the graphics device. 2638 * @param y the y-coordinate of the trace line. 2639 */ 2640 private void drawVerticalAxisTrace(Graphics2D g2, int y) { 2641 2642 Rectangle2D dataArea = getScreenDataArea(); 2643 2644 g2.setXORMode(Color.orange); 2645 if (((int) dataArea.getMinY() < y) && (y < (int) dataArea.getMaxY())) { 2646 2647 if (this.horizontalTraceLine != null) { 2648 g2.draw(this.horizontalTraceLine); 2649 this.horizontalTraceLine.setLine((int) dataArea.getMinX(), y, 2650 (int) dataArea.getMaxX(), y); 2651 } 2652 else { 2653 this.horizontalTraceLine = new Line2D.Float( 2654 (int) dataArea.getMinX(), y, (int) dataArea.getMaxX(), 2655 y); 2656 } 2657 g2.draw(this.horizontalTraceLine); 2658 } 2659 2660 // Reset to the default 'overwrite' mode 2661 g2.setPaintMode(); 2662 } 2663 2664 /** 2665 * Displays a dialog that allows the user to edit the properties for the 2666 * current chart. 2667 * 2668 * @since 1.0.3 2669 */ 2670 public void doEditChartProperties() { 2671 2672 ChartEditor editor = ChartEditorManager.getChartEditor(this.chart); 2673 int result = JOptionPane.showConfirmDialog(this, editor, 2674 localizationResources.getString("Chart_Properties"), 2675 JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); 2676 if (result == JOptionPane.OK_OPTION) { 2677 editor.updateChart(this.chart); 2678 } 2679 2680 } 2681 2682 /** 2683 * Copies the current chart to the system clipboard. 2684 * 2685 * @since 1.0.13 2686 */ 2687 public void doCopy() { 2688 Clipboard systemClipboard 2689 = Toolkit.getDefaultToolkit().getSystemClipboard(); 2690 Insets insets = getInsets(); 2691 int w = getWidth() - insets.left - insets.right; 2692 int h = getHeight() - insets.top - insets.bottom; 2693 ChartTransferable selection = new ChartTransferable(this.chart, w, h, 2694 getMinimumDrawWidth(), getMinimumDrawHeight(), 2695 getMaximumDrawWidth(), getMaximumDrawHeight(), true); 2696 systemClipboard.setContents(selection, null); 2697 } 2698 2699 /** 2700 * Opens a file chooser and gives the user an opportunity to save the chart 2701 * in PNG format. 2702 * 2703 * @throws IOException if there is an I/O error. 2704 */ 2705 public void doSaveAs() throws IOException { 2706 JFileChooser fileChooser = new JFileChooser(); 2707 fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs); 2708 FileNameExtensionFilter filter = new FileNameExtensionFilter( 2709 localizationResources.getString("PNG_Image_Files"), "png"); 2710 fileChooser.addChoosableFileFilter(filter); 2711 fileChooser.setFileFilter(filter); 2712 2713 int option = fileChooser.showSaveDialog(this); 2714 if (option == JFileChooser.APPROVE_OPTION) { 2715 String filename = fileChooser.getSelectedFile().getPath(); 2716 if (isEnforceFileExtensions()) { 2717 if (!filename.endsWith(".png")) { 2718 filename = filename + ".png"; 2719 } 2720 } 2721 ChartUtilities.saveChartAsPNG(new File(filename), this.chart, 2722 getWidth(), getHeight()); 2723 } 2724 } 2725 2726 /** 2727 * Saves the chart in SVG format (a filechooser will be displayed so that 2728 * the user can specify the filename). Note that this method only works 2729 * if the JFreeSVG library is on the classpath...if this library is not 2730 * present, the method will fail. 2731 */ 2732 private void saveAsSVG(File f) throws IOException { 2733 File file = f; 2734 if (file == null) { 2735 JFileChooser fileChooser = new JFileChooser(); 2736 fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs); 2737 FileNameExtensionFilter filter = new FileNameExtensionFilter( 2738 localizationResources.getString("SVG_Files"), "svg"); 2739 fileChooser.addChoosableFileFilter(filter); 2740 fileChooser.setFileFilter(filter); 2741 2742 int option = fileChooser.showSaveDialog(this); 2743 if (option == JFileChooser.APPROVE_OPTION) { 2744 String filename = fileChooser.getSelectedFile().getPath(); 2745 if (isEnforceFileExtensions()) { 2746 if (!filename.endsWith(".svg")) { 2747 filename = filename + ".svg"; 2748 } 2749 } 2750 file = new File(filename); 2751 if (file.exists()) { 2752 String fileExists = localizationResources.getString( 2753 "FILE_EXISTS_CONFIRM_OVERWRITE"); 2754 int response = JOptionPane.showConfirmDialog(this, 2755 fileExists, "Save As SVG", 2756 JOptionPane.OK_CANCEL_OPTION); 2757 if (response == JOptionPane.CANCEL_OPTION) { 2758 file = null; 2759 } 2760 } 2761 } 2762 } 2763 2764 if (file != null) { 2765 // use reflection to get the SVG string 2766 String svg = generateSVG(getWidth(), getHeight()); 2767 BufferedWriter writer = null; 2768 try { 2769 writer = new BufferedWriter(new FileWriter(file)); 2770 writer.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); 2771 writer.write(svg + "\n"); 2772 writer.flush(); 2773 } finally { 2774 try { 2775 if (writer != null) { 2776 writer.close(); 2777 } 2778 } catch (IOException ex) { 2779 throw new RuntimeException(ex); 2780 } 2781 } 2782 2783 } 2784 } 2785 2786 /** 2787 * Generates a string containing a rendering of the chart in SVG format. 2788 * This feature is only supported if the JFreeSVG library is included on 2789 * the classpath. 2790 * 2791 * @return A string containing an SVG element for the current chart, or 2792 * <code>null</code> if there is a problem with the method invocation 2793 * by reflection. 2794 */ 2795 private String generateSVG(int width, int height) { 2796 Graphics2D g2 = createSVGGraphics2D(width, height); 2797 if (g2 == null) { 2798 throw new IllegalStateException("JFreeSVG library is not present."); 2799 } 2800 // we suppress shadow generation, because SVG is a vector format and 2801 // the shadow effect is applied via bitmap effects... 2802 g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true); 2803 String svg = null; 2804 Rectangle2D drawArea = new Rectangle2D.Double(0, 0, width, height); 2805 this.chart.draw(g2, drawArea); 2806 try { 2807 Method m = g2.getClass().getMethod("getSVGElement"); 2808 svg = (String) m.invoke(g2); 2809 } catch (NoSuchMethodException e) { 2810 // null will be returned 2811 } catch (SecurityException e) { 2812 // null will be returned 2813 } catch (IllegalAccessException e) { 2814 // null will be returned 2815 } catch (IllegalArgumentException e) { 2816 // null will be returned 2817 } catch (InvocationTargetException e) { 2818 // null will be returned 2819 } 2820 return svg; 2821 } 2822 2823 private Graphics2D createSVGGraphics2D(int w, int h) { 2824 try { 2825 Class svgGraphics2d = Class.forName("org.jfree.graphics2d.svg.SVGGraphics2D"); 2826 Constructor ctor = svgGraphics2d.getConstructor(int.class, int.class); 2827 return (Graphics2D) ctor.newInstance(w, h); 2828 } catch (ClassNotFoundException ex) { 2829 return null; 2830 } catch (NoSuchMethodException ex) { 2831 return null; 2832 } catch (SecurityException ex) { 2833 return null; 2834 } catch (InstantiationException ex) { 2835 return null; 2836 } catch (IllegalAccessException ex) { 2837 return null; 2838 } catch (IllegalArgumentException ex) { 2839 return null; 2840 } catch (InvocationTargetException ex) { 2841 return null; 2842 } 2843 } 2844 2845 /** 2846 * Saves the chart in PDF format (a filechooser will be displayed so that 2847 * the user can specify the filename). Note that this method only works 2848 * if the OrsonPDF library is on the classpath...if this library is not 2849 * present, the method will fail. 2850 */ 2851 private void saveAsPDF(File f) { 2852 File file = f; 2853 if (file == null) { 2854 JFileChooser fileChooser = new JFileChooser(); 2855 fileChooser.setCurrentDirectory(this.defaultDirectoryForSaveAs); 2856 FileNameExtensionFilter filter = new FileNameExtensionFilter( 2857 localizationResources.getString("PDF_Files"), "pdf"); 2858 fileChooser.addChoosableFileFilter(filter); 2859 fileChooser.setFileFilter(filter); 2860 2861 int option = fileChooser.showSaveDialog(this); 2862 if (option == JFileChooser.APPROVE_OPTION) { 2863 String filename = fileChooser.getSelectedFile().getPath(); 2864 if (isEnforceFileExtensions()) { 2865 if (!filename.endsWith(".pdf")) { 2866 filename = filename + ".pdf"; 2867 } 2868 } 2869 file = new File(filename); 2870 if (file.exists()) { 2871 String fileExists = localizationResources.getString( 2872 "FILE_EXISTS_CONFIRM_OVERWRITE"); 2873 int response = JOptionPane.showConfirmDialog(this, 2874 fileExists, "Save As PDF", 2875 JOptionPane.OK_CANCEL_OPTION); 2876 if (response == JOptionPane.CANCEL_OPTION) { 2877 file = null; 2878 } 2879 } 2880 } 2881 } 2882 2883 if (file != null) { 2884 writeAsPDF(file, getWidth(), getHeight()); 2885 } 2886 } 2887 2888 /** 2889 * Returns <code>true</code> if OrsonPDF is on the classpath, and 2890 * <code>false</code> otherwise. The OrsonPDF library can be found at 2891 * http://www.object-refinery.com/pdf/ 2892 * 2893 * @return A boolean. 2894 */ 2895 private boolean isOrsonPDFAvailable() { 2896 Class pdfDocumentClass = null; 2897 try { 2898 pdfDocumentClass = Class.forName("com.orsonpdf.PDFDocument"); 2899 } catch (ClassNotFoundException e) { 2900 // pdfDocument class will be null so the function will return false 2901 } 2902 return (pdfDocumentClass != null); 2903 } 2904 2905 /** 2906 * Writes the current chart to the specified file in PDF format. This 2907 * will only work when the OrsonPDF library is found on the classpath. 2908 * Reflection is used to ensure there is no compile-time dependency on 2909 * OrsonPDF (which is non-free software). 2910 * 2911 * @param file the output file (<code>null</code> not permitted). 2912 * @param w the chart width. 2913 * @param h the chart height. 2914 */ 2915 private void writeAsPDF(File file, int w, int h) { 2916 if (!isOrsonPDFAvailable()) { 2917 throw new IllegalStateException( 2918 "OrsonPDF is not present on the classpath."); 2919 } 2920 ParamChecks.nullNotPermitted(file, "file"); 2921 try { 2922 Class pdfDocClass = Class.forName("com.orsonpdf.PDFDocument"); 2923 Object pdfDoc = pdfDocClass.newInstance(); 2924 Method m = pdfDocClass.getMethod("createPage", Rectangle2D.class); 2925 Rectangle2D rect = new Rectangle(w, h); 2926 Object page = m.invoke(pdfDoc, rect); 2927 Method m2 = page.getClass().getMethod("getGraphics2D"); 2928 Graphics2D g2 = (Graphics2D) m2.invoke(page); 2929 // we suppress shadow generation, because PDF is a vector format and 2930 // the shadow effect is applied via bitmap effects... 2931 g2.setRenderingHint(JFreeChart.KEY_SUPPRESS_SHADOW_GENERATION, true); 2932 Rectangle2D drawArea = new Rectangle2D.Double(0, 0, w, h); 2933 this.chart.draw(g2, drawArea); 2934 Method m3 = pdfDocClass.getMethod("writeToFile", File.class); 2935 m3.invoke(pdfDoc, file); 2936 } catch (ClassNotFoundException ex) { 2937 throw new RuntimeException(ex); 2938 } catch (InstantiationException ex) { 2939 throw new RuntimeException(ex); 2940 } catch (IllegalAccessException ex) { 2941 throw new RuntimeException(ex); 2942 } catch (NoSuchMethodException ex) { 2943 throw new RuntimeException(ex); 2944 } catch (SecurityException ex) { 2945 throw new RuntimeException(ex); 2946 } catch (IllegalArgumentException ex) { 2947 throw new RuntimeException(ex); 2948 } catch (InvocationTargetException ex) { 2949 throw new RuntimeException(ex); 2950 } 2951 } 2952 2953 /** 2954 * Creates a print job for the chart. 2955 */ 2956 public void createChartPrintJob() { 2957 PrinterJob job = PrinterJob.getPrinterJob(); 2958 PageFormat pf = job.defaultPage(); 2959 PageFormat pf2 = job.pageDialog(pf); 2960 if (pf2 != pf) { 2961 job.setPrintable(this, pf2); 2962 if (job.printDialog()) { 2963 try { 2964 job.print(); 2965 } 2966 catch (PrinterException e) { 2967 JOptionPane.showMessageDialog(this, e); 2968 } 2969 } 2970 } 2971 } 2972 2973 /** 2974 * Prints the chart on a single page. 2975 * 2976 * @param g the graphics context. 2977 * @param pf the page format to use. 2978 * @param pageIndex the index of the page. If not <code>0</code>, nothing 2979 * gets print. 2980 * 2981 * @return The result of printing. 2982 */ 2983 @Override 2984 public int print(Graphics g, PageFormat pf, int pageIndex) { 2985 2986 if (pageIndex != 0) { 2987 return NO_SUCH_PAGE; 2988 } 2989 Graphics2D g2 = (Graphics2D) g; 2990 double x = pf.getImageableX(); 2991 double y = pf.getImageableY(); 2992 double w = pf.getImageableWidth(); 2993 double h = pf.getImageableHeight(); 2994 this.chart.draw(g2, new Rectangle2D.Double(x, y, w, h), this.anchor, 2995 null); 2996 return PAGE_EXISTS; 2997 2998 } 2999 3000 /** 3001 * Adds a listener to the list of objects listening for chart mouse events. 3002 * 3003 * @param listener the listener (<code>null</code> not permitted). 3004 */ 3005 public void addChartMouseListener(ChartMouseListener listener) { 3006 ParamChecks.nullNotPermitted(listener, "listener"); 3007 this.chartMouseListeners.add(ChartMouseListener.class, listener); 3008 } 3009 3010 /** 3011 * Removes a listener from the list of objects listening for chart mouse 3012 * events. 3013 * 3014 * @param listener the listener. 3015 */ 3016 public void removeChartMouseListener(ChartMouseListener listener) { 3017 this.chartMouseListeners.remove(ChartMouseListener.class, listener); 3018 } 3019 3020 /** 3021 * Returns an array of the listeners of the given type registered with the 3022 * panel. 3023 * 3024 * @param listenerType the listener type. 3025 * 3026 * @return An array of listeners. 3027 */ 3028 @Override 3029 public EventListener[] getListeners(Class listenerType) { 3030 if (listenerType == ChartMouseListener.class) { 3031 // fetch listeners from local storage 3032 return this.chartMouseListeners.getListeners(listenerType); 3033 } 3034 else { 3035 return super.getListeners(listenerType); 3036 } 3037 } 3038 3039 /** 3040 * Creates a popup menu for the panel. 3041 * 3042 * @param properties include a menu item for the chart property editor. 3043 * @param save include a menu item for saving the chart. 3044 * @param print include a menu item for printing the chart. 3045 * @param zoom include menu items for zooming. 3046 * 3047 * @return The popup menu. 3048 */ 3049 protected JPopupMenu createPopupMenu(boolean properties, boolean save, 3050 boolean print, boolean zoom) { 3051 return createPopupMenu(properties, false, save, print, zoom); 3052 } 3053 3054 /** 3055 * Creates a popup menu for the panel. 3056 * 3057 * @param properties include a menu item for the chart property editor. 3058 * @param copy include a menu item for copying to the clipboard. 3059 * @param save include a menu item for saving the chart. 3060 * @param print include a menu item for printing the chart. 3061 * @param zoom include menu items for zooming. 3062 * 3063 * @return The popup menu. 3064 * 3065 * @since 1.0.13 3066 */ 3067 protected JPopupMenu createPopupMenu(boolean properties, 3068 boolean copy, boolean save, boolean print, boolean zoom) { 3069 3070 JPopupMenu result = new JPopupMenu(localizationResources.getString("Chart") + ":"); 3071 boolean separator = false; 3072 3073 if (properties) { 3074 JMenuItem propertiesItem = new JMenuItem( 3075 localizationResources.getString("Properties...")); 3076 propertiesItem.setActionCommand(PROPERTIES_COMMAND); 3077 propertiesItem.addActionListener(this); 3078 result.add(propertiesItem); 3079 separator = true; 3080 } 3081 3082 if (copy) { 3083 if (separator) { 3084 result.addSeparator(); 3085 } 3086 JMenuItem copyItem = new JMenuItem( 3087 localizationResources.getString("Copy")); 3088 copyItem.setActionCommand(COPY_COMMAND); 3089 copyItem.addActionListener(this); 3090 result.add(copyItem); 3091 separator = !save; 3092 } 3093 3094 if (save) { 3095 if (separator) { 3096 result.addSeparator(); 3097 } 3098 JMenu saveSubMenu = new JMenu(localizationResources.getString( 3099 "Save_as")); 3100 JMenuItem pngItem = new JMenuItem(localizationResources.getString( 3101 "PNG...")); 3102 pngItem.setActionCommand("SAVE_AS_PNG"); 3103 pngItem.addActionListener(this); 3104 saveSubMenu.add(pngItem); 3105 3106 if (createSVGGraphics2D(10, 10) != null) { 3107 JMenuItem svgItem = new JMenuItem(localizationResources.getString( 3108 "SVG...")); 3109 svgItem.setActionCommand("SAVE_AS_SVG"); 3110 svgItem.addActionListener(this); 3111 saveSubMenu.add(svgItem); 3112 } 3113 3114 if (isOrsonPDFAvailable()) { 3115 JMenuItem pdfItem = new JMenuItem( 3116 localizationResources.getString("PDF...")); 3117 pdfItem.setActionCommand("SAVE_AS_PDF"); 3118 pdfItem.addActionListener(this); 3119 saveSubMenu.add(pdfItem); 3120 } 3121 result.add(saveSubMenu); 3122 separator = true; 3123 } 3124 3125 if (print) { 3126 if (separator) { 3127 result.addSeparator(); 3128 } 3129 JMenuItem printItem = new JMenuItem( 3130 localizationResources.getString("Print...")); 3131 printItem.setActionCommand(PRINT_COMMAND); 3132 printItem.addActionListener(this); 3133 result.add(printItem); 3134 separator = true; 3135 } 3136 3137 if (zoom) { 3138 if (separator) { 3139 result.addSeparator(); 3140 } 3141 3142 JMenu zoomInMenu = new JMenu( 3143 localizationResources.getString("Zoom_In")); 3144 3145 this.zoomInBothMenuItem = new JMenuItem( 3146 localizationResources.getString("All_Axes")); 3147 this.zoomInBothMenuItem.setActionCommand(ZOOM_IN_BOTH_COMMAND); 3148 this.zoomInBothMenuItem.addActionListener(this); 3149 zoomInMenu.add(this.zoomInBothMenuItem); 3150 3151 zoomInMenu.addSeparator(); 3152 3153 this.zoomInDomainMenuItem = new JMenuItem( 3154 localizationResources.getString("Domain_Axis")); 3155 this.zoomInDomainMenuItem.setActionCommand(ZOOM_IN_DOMAIN_COMMAND); 3156 this.zoomInDomainMenuItem.addActionListener(this); 3157 zoomInMenu.add(this.zoomInDomainMenuItem); 3158 3159 this.zoomInRangeMenuItem = new JMenuItem( 3160 localizationResources.getString("Range_Axis")); 3161 this.zoomInRangeMenuItem.setActionCommand(ZOOM_IN_RANGE_COMMAND); 3162 this.zoomInRangeMenuItem.addActionListener(this); 3163 zoomInMenu.add(this.zoomInRangeMenuItem); 3164 3165 result.add(zoomInMenu); 3166 3167 JMenu zoomOutMenu = new JMenu( 3168 localizationResources.getString("Zoom_Out")); 3169 3170 this.zoomOutBothMenuItem = new JMenuItem( 3171 localizationResources.getString("All_Axes")); 3172 this.zoomOutBothMenuItem.setActionCommand(ZOOM_OUT_BOTH_COMMAND); 3173 this.zoomOutBothMenuItem.addActionListener(this); 3174 zoomOutMenu.add(this.zoomOutBothMenuItem); 3175 3176 zoomOutMenu.addSeparator(); 3177 3178 this.zoomOutDomainMenuItem = new JMenuItem( 3179 localizationResources.getString("Domain_Axis")); 3180 this.zoomOutDomainMenuItem.setActionCommand( 3181 ZOOM_OUT_DOMAIN_COMMAND); 3182 this.zoomOutDomainMenuItem.addActionListener(this); 3183 zoomOutMenu.add(this.zoomOutDomainMenuItem); 3184 3185 this.zoomOutRangeMenuItem = new JMenuItem( 3186 localizationResources.getString("Range_Axis")); 3187 this.zoomOutRangeMenuItem.setActionCommand(ZOOM_OUT_RANGE_COMMAND); 3188 this.zoomOutRangeMenuItem.addActionListener(this); 3189 zoomOutMenu.add(this.zoomOutRangeMenuItem); 3190 3191 result.add(zoomOutMenu); 3192 3193 JMenu autoRangeMenu = new JMenu( 3194 localizationResources.getString("Auto_Range")); 3195 3196 this.zoomResetBothMenuItem = new JMenuItem( 3197 localizationResources.getString("All_Axes")); 3198 this.zoomResetBothMenuItem.setActionCommand( 3199 ZOOM_RESET_BOTH_COMMAND); 3200 this.zoomResetBothMenuItem.addActionListener(this); 3201 autoRangeMenu.add(this.zoomResetBothMenuItem); 3202 3203 autoRangeMenu.addSeparator(); 3204 this.zoomResetDomainMenuItem = new JMenuItem( 3205 localizationResources.getString("Domain_Axis")); 3206 this.zoomResetDomainMenuItem.setActionCommand( 3207 ZOOM_RESET_DOMAIN_COMMAND); 3208 this.zoomResetDomainMenuItem.addActionListener(this); 3209 autoRangeMenu.add(this.zoomResetDomainMenuItem); 3210 3211 this.zoomResetRangeMenuItem = new JMenuItem( 3212 localizationResources.getString("Range_Axis")); 3213 this.zoomResetRangeMenuItem.setActionCommand( 3214 ZOOM_RESET_RANGE_COMMAND); 3215 this.zoomResetRangeMenuItem.addActionListener(this); 3216 autoRangeMenu.add(this.zoomResetRangeMenuItem); 3217 3218 result.addSeparator(); 3219 result.add(autoRangeMenu); 3220 3221 } 3222 3223 return result; 3224 3225 } 3226 3227 /** 3228 * The idea is to modify the zooming options depending on the type of chart 3229 * being displayed by the panel. 3230 * 3231 * @param x horizontal position of the popup. 3232 * @param y vertical position of the popup. 3233 */ 3234 protected void displayPopupMenu(int x, int y) { 3235 3236 if (this.popup == null) { 3237 return; 3238 } 3239 3240 // go through each zoom menu item and decide whether or not to 3241 // enable it... 3242 boolean isDomainZoomable = false; 3243 boolean isRangeZoomable = false; 3244 Plot plot = (this.chart != null ? this.chart.getPlot() : null); 3245 if (plot instanceof Zoomable) { 3246 Zoomable z = (Zoomable) plot; 3247 isDomainZoomable = z.isDomainZoomable(); 3248 isRangeZoomable = z.isRangeZoomable(); 3249 } 3250 3251 if (this.zoomInDomainMenuItem != null) { 3252 this.zoomInDomainMenuItem.setEnabled(isDomainZoomable); 3253 } 3254 if (this.zoomOutDomainMenuItem != null) { 3255 this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable); 3256 } 3257 if (this.zoomResetDomainMenuItem != null) { 3258 this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable); 3259 } 3260 3261 if (this.zoomInRangeMenuItem != null) { 3262 this.zoomInRangeMenuItem.setEnabled(isRangeZoomable); 3263 } 3264 if (this.zoomOutRangeMenuItem != null) { 3265 this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable); 3266 } 3267 3268 if (this.zoomResetRangeMenuItem != null) { 3269 this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable); 3270 } 3271 3272 if (this.zoomInBothMenuItem != null) { 3273 this.zoomInBothMenuItem.setEnabled(isDomainZoomable 3274 && isRangeZoomable); 3275 } 3276 if (this.zoomOutBothMenuItem != null) { 3277 this.zoomOutBothMenuItem.setEnabled(isDomainZoomable 3278 && isRangeZoomable); 3279 } 3280 if (this.zoomResetBothMenuItem != null) { 3281 this.zoomResetBothMenuItem.setEnabled(isDomainZoomable 3282 && isRangeZoomable); 3283 } 3284 3285 this.popup.show(this, x, y); 3286 3287 } 3288 3289 /** 3290 * Updates the UI for a LookAndFeel change. 3291 */ 3292 @Override 3293 public void updateUI() { 3294 // here we need to update the UI for the popup menu, if the panel 3295 // has one... 3296 if (this.popup != null) { 3297 SwingUtilities.updateComponentTreeUI(this.popup); 3298 } 3299 super.updateUI(); 3300 } 3301 3302 /** 3303 * Provides serialization support. 3304 * 3305 * @param stream the output stream. 3306 * 3307 * @throws IOException if there is an I/O error. 3308 */ 3309 private void writeObject(ObjectOutputStream stream) throws IOException { 3310 stream.defaultWriteObject(); 3311 SerialUtilities.writePaint(this.zoomFillPaint, stream); 3312 SerialUtilities.writePaint(this.zoomOutlinePaint, stream); 3313 } 3314 3315 /** 3316 * Provides serialization support. 3317 * 3318 * @param stream the input stream. 3319 * 3320 * @throws IOException if there is an I/O error. 3321 * @throws ClassNotFoundException if there is a classpath problem. 3322 */ 3323 private void readObject(ObjectInputStream stream) 3324 throws IOException, ClassNotFoundException { 3325 stream.defaultReadObject(); 3326 this.zoomFillPaint = SerialUtilities.readPaint(stream); 3327 this.zoomOutlinePaint = SerialUtilities.readPaint(stream); 3328 3329 // we create a new but empty chartMouseListeners list 3330 this.chartMouseListeners = new EventListenerList(); 3331 3332 // register as a listener with sub-components... 3333 if (this.chart != null) { 3334 this.chart.addChangeListener(this); 3335 } 3336 3337 } 3338 3339}