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