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