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