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