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