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