001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * --------- 028 * Plot.java 029 * --------- 030 * (C) Copyright 2000-2020, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Sylvain Vieujot; 034 * Jeremy Bowman; 035 * Andreas Schneider; 036 * Gideon Krause; 037 * Nicolas Brodu; 038 * Michal Krause; 039 * Richard West, Advanced Micro Devices, Inc.; 040 * Peter Kolb - patches 2603321, 2809117; 041 * 042 */ 043 044package org.jfree.chart.plot; 045 046import java.awt.AlphaComposite; 047import java.awt.BasicStroke; 048import java.awt.Color; 049import java.awt.Composite; 050import java.awt.Font; 051import java.awt.GradientPaint; 052import java.awt.Graphics2D; 053import java.awt.Image; 054import java.awt.Paint; 055import java.awt.RenderingHints; 056import java.awt.Shape; 057import java.awt.Stroke; 058import java.awt.geom.Ellipse2D; 059import java.awt.geom.Point2D; 060import java.awt.geom.Rectangle2D; 061import java.io.IOException; 062import java.io.ObjectInputStream; 063import java.io.ObjectOutputStream; 064import java.io.Serializable; 065 066import javax.swing.event.EventListenerList; 067 068import org.jfree.chart.JFreeChart; 069import org.jfree.chart.LegendItemCollection; 070import org.jfree.chart.LegendItemSource; 071import org.jfree.chart.annotations.Annotation; 072import org.jfree.chart.axis.AxisLocation; 073import org.jfree.chart.entity.EntityCollection; 074import org.jfree.chart.entity.PlotEntity; 075import org.jfree.chart.event.AnnotationChangeEvent; 076import org.jfree.chart.event.AnnotationChangeListener; 077import org.jfree.chart.event.AxisChangeEvent; 078import org.jfree.chart.event.AxisChangeListener; 079import org.jfree.chart.event.ChartChangeEventType; 080import org.jfree.chart.event.MarkerChangeEvent; 081import org.jfree.chart.event.MarkerChangeListener; 082import org.jfree.chart.event.PlotChangeEvent; 083import org.jfree.chart.event.PlotChangeListener; 084import org.jfree.chart.text.G2TextMeasurer; 085import org.jfree.chart.text.TextBlock; 086import org.jfree.chart.text.TextBlockAnchor; 087import org.jfree.chart.text.TextUtils; 088import org.jfree.chart.ui.Align; 089import org.jfree.chart.ui.RectangleEdge; 090import org.jfree.chart.ui.RectangleInsets; 091import org.jfree.chart.util.ObjectUtils; 092import org.jfree.chart.util.PaintUtils; 093import org.jfree.chart.util.Args; 094import org.jfree.chart.util.PublicCloneable; 095import org.jfree.chart.util.SerialUtils; 096import org.jfree.data.general.DatasetChangeEvent; 097import org.jfree.data.general.DatasetChangeListener; 098import org.jfree.data.general.DatasetGroup; 099 100/** 101 * The base class for all plots in JFreeChart. The {@link JFreeChart} class 102 * delegates the drawing of axes and data to the plot. This base class 103 * provides facilities common to most plot types. 104 */ 105public abstract class Plot implements AxisChangeListener, 106 DatasetChangeListener, AnnotationChangeListener, MarkerChangeListener, 107 LegendItemSource, PublicCloneable, Cloneable, Serializable { 108 109 /** For serialization. */ 110 private static final long serialVersionUID = -8831571430103671324L; 111 112 /** Useful constant representing zero. */ 113 public static final Number ZERO = 0; 114 115 /** The default insets. */ 116 public static final RectangleInsets DEFAULT_INSETS 117 = new RectangleInsets(4.0, 8.0, 4.0, 8.0); 118 119 /** The default outline stroke. */ 120 public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f, 121 BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 122 123 /** The default outline color. */ 124 public static final Paint DEFAULT_OUTLINE_PAINT = Color.GRAY; 125 126 /** The default foreground alpha transparency. */ 127 public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f; 128 129 /** The default background alpha transparency. */ 130 public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f; 131 132 /** The default background color. */ 133 public static final Paint DEFAULT_BACKGROUND_PAINT = Color.WHITE; 134 135 /** The minimum width at which the plot should be drawn. */ 136 public static final int MINIMUM_WIDTH_TO_DRAW = 10; 137 138 /** The minimum height at which the plot should be drawn. */ 139 public static final int MINIMUM_HEIGHT_TO_DRAW = 10; 140 141 /** A default box shape for legend items. */ 142 public static final Shape DEFAULT_LEGEND_ITEM_BOX 143 = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0); 144 145 /** A default circle shape for legend items. */ 146 public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE 147 = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0); 148 149 /** 150 * The chart that the plot is assigned to. It can be {@code null} if the 151 * plot is not assigned to a chart yet, or if the plot is a subplot of a 152 * another plot. 153 * 154 * @since 1.0.20 155 */ 156 private JFreeChart chart; 157 158 /** The parent plot ({@code null} if this is the root plot). */ 159 private Plot parent; 160 161 /** The dataset group (to be used for thread synchronisation). */ 162 private DatasetGroup datasetGroup; 163 164 /** The message to display if no data is available. */ 165 private String noDataMessage; 166 167 /** The font used to display the 'no data' message. */ 168 private Font noDataMessageFont; 169 170 /** The paint used to draw the 'no data' message. */ 171 private transient Paint noDataMessagePaint; 172 173 /** Amount of blank space around the plot area. */ 174 private RectangleInsets insets; 175 176 /** 177 * A flag that controls whether or not the plot outline is drawn. 178 * 179 * @since 1.0.6 180 */ 181 private boolean outlineVisible; 182 183 /** The Stroke used to draw an outline around the plot. */ 184 private transient Stroke outlineStroke; 185 186 /** The Paint used to draw an outline around the plot. */ 187 private transient Paint outlinePaint; 188 189 /** An optional color used to fill the plot background. */ 190 private transient Paint backgroundPaint; 191 192 /** An optional image for the plot background. */ 193 private transient Image backgroundImage; // not currently serialized 194 195 /** The alignment for the background image. */ 196 private int backgroundImageAlignment = Align.FIT; 197 198 /** The alpha value used to draw the background image. */ 199 private float backgroundImageAlpha = 0.5f; 200 201 /** The alpha-transparency for the plot. */ 202 private float foregroundAlpha; 203 204 /** The alpha transparency for the background paint. */ 205 private float backgroundAlpha; 206 207 /** The drawing supplier. */ 208 private DrawingSupplier drawingSupplier; 209 210 /** Storage for registered change listeners. */ 211 private transient EventListenerList listenerList; 212 213 /** 214 * A flag that controls whether or not the plot will notify listeners 215 * of changes (defaults to true, but sometimes it is useful to disable 216 * this). 217 * 218 * @since 1.0.13 219 */ 220 private boolean notify; 221 222 /** 223 * Creates a new plot. 224 */ 225 protected Plot() { 226 this.chart = null; 227 this.parent = null; 228 this.insets = DEFAULT_INSETS; 229 this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; 230 this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA; 231 this.backgroundImage = null; 232 this.outlineVisible = true; 233 this.outlineStroke = DEFAULT_OUTLINE_STROKE; 234 this.outlinePaint = DEFAULT_OUTLINE_PAINT; 235 this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA; 236 237 this.noDataMessage = null; 238 this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12); 239 this.noDataMessagePaint = Color.BLACK; 240 241 this.drawingSupplier = new DefaultDrawingSupplier(); 242 243 this.notify = true; 244 this.listenerList = new EventListenerList(); 245 } 246 247 /** 248 * Returns the chart that this plot is assigned to. This method can 249 * return {@code null} if the plot is not yet assigned to a plot, or if the 250 * plot is a subplot of another plot. 251 * 252 * @return The chart (possibly {@code null}). 253 * 254 * @since 1.0.20 255 */ 256 public JFreeChart getChart() { 257 return this.chart; 258 } 259 260 /** 261 * Sets the chart that the plot is assigned to. This method is not 262 * intended for external use. 263 * 264 * @param chart the chart ({@code null} permitted). 265 * 266 * @since 1.0.20 267 */ 268 public void setChart(JFreeChart chart) { 269 this.chart = chart; 270 } 271 272 /** 273 * Fetches the element hinting flag from the chart that this plot is 274 * assigned to. If the plot is not assigned (directly or indirectly) to 275 * a chart instance, this method will return {@code false}. 276 * 277 * @return A boolean. 278 * 279 * @since 1.0.20 280 */ 281 public boolean fetchElementHintingFlag() { 282 if (this.parent != null) { 283 return this.parent.fetchElementHintingFlag(); 284 } 285 if (this.chart != null) { 286 return this.chart.getElementHinting(); 287 } 288 return false; 289 } 290 291 /** 292 * Returns the dataset group for the plot (not currently used). 293 * 294 * @return The dataset group. 295 * 296 * @see #setDatasetGroup(DatasetGroup) 297 */ 298 public DatasetGroup getDatasetGroup() { 299 return this.datasetGroup; 300 } 301 302 /** 303 * Sets the dataset group (not currently used). 304 * 305 * @param group the dataset group ({@code null} permitted). 306 * 307 * @see #getDatasetGroup() 308 */ 309 protected void setDatasetGroup(DatasetGroup group) { 310 this.datasetGroup = group; 311 } 312 313 /** 314 * Returns the string that is displayed when the dataset is empty or 315 * {@code null}. 316 * 317 * @return The 'no data' message ({@code null} possible). 318 * 319 * @see #setNoDataMessage(String) 320 * @see #getNoDataMessageFont() 321 * @see #getNoDataMessagePaint() 322 */ 323 public String getNoDataMessage() { 324 return this.noDataMessage; 325 } 326 327 /** 328 * Sets the message that is displayed when the dataset is empty or 329 * {@code null}, and sends a {@link PlotChangeEvent} to all registered 330 * listeners. 331 * 332 * @param message the message ({@code null} permitted). 333 * 334 * @see #getNoDataMessage() 335 */ 336 public void setNoDataMessage(String message) { 337 this.noDataMessage = message; 338 fireChangeEvent(); 339 } 340 341 /** 342 * Returns the font used to display the 'no data' message. 343 * 344 * @return The font (never {@code null}). 345 * 346 * @see #setNoDataMessageFont(Font) 347 * @see #getNoDataMessage() 348 */ 349 public Font getNoDataMessageFont() { 350 return this.noDataMessageFont; 351 } 352 353 /** 354 * Sets the font used to display the 'no data' message and sends a 355 * {@link PlotChangeEvent} to all registered listeners. 356 * 357 * @param font the font ({@code null} not permitted). 358 * 359 * @see #getNoDataMessageFont() 360 */ 361 public void setNoDataMessageFont(Font font) { 362 Args.nullNotPermitted(font, "font"); 363 this.noDataMessageFont = font; 364 fireChangeEvent(); 365 } 366 367 /** 368 * Returns the paint used to display the 'no data' message. 369 * 370 * @return The paint (never {@code null}). 371 * 372 * @see #setNoDataMessagePaint(Paint) 373 * @see #getNoDataMessage() 374 */ 375 public Paint getNoDataMessagePaint() { 376 return this.noDataMessagePaint; 377 } 378 379 /** 380 * Sets the paint used to display the 'no data' message and sends a 381 * {@link PlotChangeEvent} to all registered listeners. 382 * 383 * @param paint the paint ({@code null} not permitted). 384 * 385 * @see #getNoDataMessagePaint() 386 */ 387 public void setNoDataMessagePaint(Paint paint) { 388 Args.nullNotPermitted(paint, "paint"); 389 this.noDataMessagePaint = paint; 390 fireChangeEvent(); 391 } 392 393 /** 394 * Returns a short string describing the plot type. 395 * <P> 396 * Note: this gets used in the chart property editing user interface, 397 * but there needs to be a better mechanism for identifying the plot type. 398 * 399 * @return A short string describing the plot type (never 400 * {@code null}). 401 */ 402 public abstract String getPlotType(); 403 404 /** 405 * Returns the parent plot (or {@code null} if this plot is not part 406 * of a combined plot). 407 * 408 * @return The parent plot. 409 * 410 * @see #setParent(Plot) 411 * @see #getRootPlot() 412 */ 413 public Plot getParent() { 414 return this.parent; 415 } 416 417 /** 418 * Sets the parent plot. This method is intended for internal use, you 419 * shouldn't need to call it directly. 420 * 421 * @param parent the parent plot ({@code null} permitted). 422 * 423 * @see #getParent() 424 */ 425 public void setParent(Plot parent) { 426 this.parent = parent; 427 } 428 429 /** 430 * Returns the root plot. 431 * 432 * @return The root plot. 433 * 434 * @see #getParent() 435 */ 436 public Plot getRootPlot() { 437 438 Plot p = getParent(); 439 if (p == null) { 440 return this; 441 } 442 return p.getRootPlot(); 443 444 } 445 446 /** 447 * Returns {@code true} if this plot is part of a combined plot 448 * structure (that is, {@link #getParent()} returns a non-{@code null} 449 * value), and {@code false} otherwise. 450 * 451 * @return {@code true} if this plot is part of a combined plot 452 * structure. 453 * 454 * @see #getParent() 455 */ 456 public boolean isSubplot() { 457 return (getParent() != null); 458 } 459 460 /** 461 * Returns the insets for the plot area. 462 * 463 * @return The insets (never {@code null}). 464 * 465 * @see #setInsets(RectangleInsets) 466 */ 467 public RectangleInsets getInsets() { 468 return this.insets; 469 } 470 471 /** 472 * Sets the insets for the plot and sends a {@link PlotChangeEvent} to 473 * all registered listeners. 474 * 475 * @param insets the new insets ({@code null} not permitted). 476 * 477 * @see #getInsets() 478 * @see #setInsets(RectangleInsets, boolean) 479 */ 480 public void setInsets(RectangleInsets insets) { 481 setInsets(insets, true); 482 } 483 484 /** 485 * Sets the insets for the plot and, if requested, and sends a 486 * {@link PlotChangeEvent} to all registered listeners. 487 * 488 * @param insets the new insets ({@code null} not permitted). 489 * @param notify a flag that controls whether the registered listeners are 490 * notified. 491 * 492 * @see #getInsets() 493 * @see #setInsets(RectangleInsets) 494 */ 495 public void setInsets(RectangleInsets insets, boolean notify) { 496 Args.nullNotPermitted(insets, "insets"); 497 if (!this.insets.equals(insets)) { 498 this.insets = insets; 499 if (notify) { 500 fireChangeEvent(); 501 } 502 } 503 504 } 505 506 /** 507 * Returns the background color of the plot area. 508 * 509 * @return The paint (possibly {@code null}). 510 * 511 * @see #setBackgroundPaint(Paint) 512 */ 513 public Paint getBackgroundPaint() { 514 return this.backgroundPaint; 515 } 516 517 /** 518 * Sets the background color of the plot area and sends a 519 * {@link PlotChangeEvent} to all registered listeners. 520 * 521 * @param paint the paint ({@code null} permitted). 522 * 523 * @see #getBackgroundPaint() 524 */ 525 public void setBackgroundPaint(Paint paint) { 526 527 if (paint == null) { 528 if (this.backgroundPaint != null) { 529 this.backgroundPaint = null; 530 fireChangeEvent(); 531 } 532 } 533 else { 534 if (this.backgroundPaint != null) { 535 if (this.backgroundPaint.equals(paint)) { 536 return; // nothing to do 537 } 538 } 539 this.backgroundPaint = paint; 540 fireChangeEvent(); 541 } 542 543 } 544 545 /** 546 * Returns the alpha transparency of the plot area background. 547 * 548 * @return The alpha transparency. 549 * 550 * @see #setBackgroundAlpha(float) 551 */ 552 public float getBackgroundAlpha() { 553 return this.backgroundAlpha; 554 } 555 556 /** 557 * Sets the alpha transparency of the plot area background, and notifies 558 * registered listeners that the plot has been modified. 559 * 560 * @param alpha the new alpha value (in the range 0.0f to 1.0f). 561 * 562 * @see #getBackgroundAlpha() 563 */ 564 public void setBackgroundAlpha(float alpha) { 565 if (this.backgroundAlpha != alpha) { 566 this.backgroundAlpha = alpha; 567 fireChangeEvent(); 568 } 569 } 570 571 /** 572 * Returns the drawing supplier for the plot. 573 * 574 * @return The drawing supplier (possibly {@code null}). 575 * 576 * @see #setDrawingSupplier(DrawingSupplier) 577 */ 578 public DrawingSupplier getDrawingSupplier() { 579 DrawingSupplier result; 580 Plot p = getParent(); 581 if (p != null) { 582 result = p.getDrawingSupplier(); 583 } 584 else { 585 result = this.drawingSupplier; 586 } 587 return result; 588 } 589 590 /** 591 * Sets the drawing supplier for the plot and sends a 592 * {@link PlotChangeEvent} to all registered listeners. The drawing 593 * supplier is responsible for supplying a limitless (possibly repeating) 594 * sequence of {@code Paint}, {@code Stroke} and 595 * {@code Shape} objects that the plot's renderer(s) can use to 596 * populate its (their) tables. 597 * 598 * @param supplier the new supplier. 599 * 600 * @see #getDrawingSupplier() 601 */ 602 public void setDrawingSupplier(DrawingSupplier supplier) { 603 this.drawingSupplier = supplier; 604 fireChangeEvent(); 605 } 606 607 /** 608 * Sets the drawing supplier for the plot and, if requested, sends a 609 * {@link PlotChangeEvent} to all registered listeners. The drawing 610 * supplier is responsible for supplying a limitless (possibly repeating) 611 * sequence of {@code Paint}, {@code Stroke} and 612 * {@code Shape} objects that the plot's renderer(s) can use to 613 * populate its (their) tables. 614 * 615 * @param supplier the new supplier. 616 * @param notify notify listeners? 617 * 618 * @see #getDrawingSupplier() 619 * 620 * @since 1.0.11 621 */ 622 public void setDrawingSupplier(DrawingSupplier supplier, boolean notify) { 623 this.drawingSupplier = supplier; 624 if (notify) { 625 fireChangeEvent(); 626 } 627 } 628 629 /** 630 * Returns the background image that is used to fill the plot's background 631 * area. 632 * 633 * @return The image (possibly {@code null}). 634 * 635 * @see #setBackgroundImage(Image) 636 */ 637 public Image getBackgroundImage() { 638 return this.backgroundImage; 639 } 640 641 /** 642 * Sets the background image for the plot and sends a 643 * {@link PlotChangeEvent} to all registered listeners. 644 * 645 * @param image the image ({@code null} permitted). 646 * 647 * @see #getBackgroundImage() 648 */ 649 public void setBackgroundImage(Image image) { 650 this.backgroundImage = image; 651 fireChangeEvent(); 652 } 653 654 /** 655 * Returns the background image alignment. Alignment constants are defined 656 * in the {@code Align} class. 657 * 658 * @return The alignment. 659 * 660 * @see #setBackgroundImageAlignment(int) 661 */ 662 public int getBackgroundImageAlignment() { 663 return this.backgroundImageAlignment; 664 } 665 666 /** 667 * Sets the alignment for the background image and sends a 668 * {@link PlotChangeEvent} to all registered listeners. Alignment options 669 * are defined by the {@link org.jfree.chart.ui.Align} class. 670 * 671 * @param alignment the alignment. 672 * 673 * @see #getBackgroundImageAlignment() 674 */ 675 public void setBackgroundImageAlignment(int alignment) { 676 if (this.backgroundImageAlignment != alignment) { 677 this.backgroundImageAlignment = alignment; 678 fireChangeEvent(); 679 } 680 } 681 682 /** 683 * Returns the alpha transparency used to draw the background image. This 684 * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent 685 * and 1.0f is fully opaque. 686 * 687 * @return The alpha transparency. 688 * 689 * @see #setBackgroundImageAlpha(float) 690 */ 691 public float getBackgroundImageAlpha() { 692 return this.backgroundImageAlpha; 693 } 694 695 /** 696 * Sets the alpha transparency used when drawing the background image. 697 * 698 * @param alpha the alpha transparency (in the range 0.0f to 1.0f, where 699 * 0.0f is fully transparent, and 1.0f is fully opaque). 700 * 701 * @throws IllegalArgumentException if {@code alpha} is not within 702 * the specified range. 703 * 704 * @see #getBackgroundImageAlpha() 705 */ 706 public void setBackgroundImageAlpha(float alpha) { 707 if (alpha < 0.0f || alpha > 1.0f) { 708 throw new IllegalArgumentException( 709 "The 'alpha' value must be in the range 0.0f to 1.0f."); 710 } 711 if (this.backgroundImageAlpha != alpha) { 712 this.backgroundImageAlpha = alpha; 713 fireChangeEvent(); 714 } 715 } 716 717 /** 718 * Returns the flag that controls whether or not the plot outline is 719 * drawn. The default value is {@code true}. Note that for 720 * historical reasons, the plot's outline paint and stroke can take on 721 * {@code null} values, in which case the outline will not be drawn 722 * even if this flag is set to {@code true}. 723 * 724 * @return The outline visibility flag. 725 * 726 * @since 1.0.6 727 * 728 * @see #setOutlineVisible(boolean) 729 */ 730 public boolean isOutlineVisible() { 731 return this.outlineVisible; 732 } 733 734 /** 735 * Sets the flag that controls whether or not the plot's outline is 736 * drawn, and sends a {@link PlotChangeEvent} to all registered listeners. 737 * 738 * @param visible the new flag value. 739 * 740 * @since 1.0.6 741 * 742 * @see #isOutlineVisible() 743 */ 744 public void setOutlineVisible(boolean visible) { 745 this.outlineVisible = visible; 746 fireChangeEvent(); 747 } 748 749 /** 750 * Returns the stroke used to outline the plot area. 751 * 752 * @return The stroke (possibly {@code null}). 753 * 754 * @see #setOutlineStroke(Stroke) 755 */ 756 public Stroke getOutlineStroke() { 757 return this.outlineStroke; 758 } 759 760 /** 761 * Sets the stroke used to outline the plot area and sends a 762 * {@link PlotChangeEvent} to all registered listeners. If you set this 763 * attribute to {@code null}, no outline will be drawn. 764 * 765 * @param stroke the stroke ({@code null} permitted). 766 * 767 * @see #getOutlineStroke() 768 */ 769 public void setOutlineStroke(Stroke stroke) { 770 if (stroke == null) { 771 if (this.outlineStroke != null) { 772 this.outlineStroke = null; 773 fireChangeEvent(); 774 } 775 } 776 else { 777 if (this.outlineStroke != null) { 778 if (this.outlineStroke.equals(stroke)) { 779 return; // nothing to do 780 } 781 } 782 this.outlineStroke = stroke; 783 fireChangeEvent(); 784 } 785 } 786 787 /** 788 * Returns the color used to draw the outline of the plot area. 789 * 790 * @return The color (possibly {@code null}). 791 * 792 * @see #setOutlinePaint(Paint) 793 */ 794 public Paint getOutlinePaint() { 795 return this.outlinePaint; 796 } 797 798 /** 799 * Sets the paint used to draw the outline of the plot area and sends a 800 * {@link PlotChangeEvent} to all registered listeners. If you set this 801 * attribute to {@code null}, no outline will be drawn. 802 * 803 * @param paint the paint ({@code null} permitted). 804 * 805 * @see #getOutlinePaint() 806 */ 807 public void setOutlinePaint(Paint paint) { 808 if (paint == null) { 809 if (this.outlinePaint != null) { 810 this.outlinePaint = null; 811 fireChangeEvent(); 812 } 813 } 814 else { 815 if (this.outlinePaint != null) { 816 if (this.outlinePaint.equals(paint)) { 817 return; // nothing to do 818 } 819 } 820 this.outlinePaint = paint; 821 fireChangeEvent(); 822 } 823 } 824 825 /** 826 * Returns the alpha-transparency for the plot foreground. 827 * 828 * @return The alpha-transparency. 829 * 830 * @see #setForegroundAlpha(float) 831 */ 832 public float getForegroundAlpha() { 833 return this.foregroundAlpha; 834 } 835 836 /** 837 * Sets the alpha-transparency for the plot and sends a 838 * {@link PlotChangeEvent} to all registered listeners. 839 * 840 * @param alpha the new alpha transparency. 841 * 842 * @see #getForegroundAlpha() 843 */ 844 public void setForegroundAlpha(float alpha) { 845 if (this.foregroundAlpha != alpha) { 846 this.foregroundAlpha = alpha; 847 fireChangeEvent(); 848 } 849 } 850 851 /** 852 * Returns the legend items for the plot. By default, this method returns 853 * {@code null}. Subclasses should override to return a 854 * {@link LegendItemCollection}. 855 * 856 * @return The legend items for the plot (possibly {@code null}). 857 */ 858 @Override 859 public LegendItemCollection getLegendItems() { 860 return null; 861 } 862 863 /** 864 * Returns a flag that controls whether or not change events are sent to 865 * registered listeners. 866 * 867 * @return A boolean. 868 * 869 * @see #setNotify(boolean) 870 * 871 * @since 1.0.13 872 */ 873 public boolean isNotify() { 874 return this.notify; 875 } 876 877 /** 878 * Sets a flag that controls whether or not listeners receive 879 * {@link PlotChangeEvent} notifications. 880 * 881 * @param notify a boolean. 882 * 883 * @see #isNotify() 884 * 885 * @since 1.0.13 886 */ 887 public void setNotify(boolean notify) { 888 this.notify = notify; 889 // if the flag is being set to true, there may be queued up changes... 890 if (notify) { 891 notifyListeners(new PlotChangeEvent(this)); 892 } 893 } 894 895 /** 896 * Registers an object for notification of changes to the plot. 897 * 898 * @param listener the object to be registered. 899 * 900 * @see #removeChangeListener(PlotChangeListener) 901 */ 902 public void addChangeListener(PlotChangeListener listener) { 903 this.listenerList.add(PlotChangeListener.class, listener); 904 } 905 906 /** 907 * Unregisters an object for notification of changes to the plot. 908 * 909 * @param listener the object to be unregistered. 910 * 911 * @see #addChangeListener(PlotChangeListener) 912 */ 913 public void removeChangeListener(PlotChangeListener listener) { 914 this.listenerList.remove(PlotChangeListener.class, listener); 915 } 916 917 /** 918 * Notifies all registered listeners that the plot has been modified. 919 * 920 * @param event information about the change event. 921 */ 922 public void notifyListeners(PlotChangeEvent event) { 923 // if the 'notify' flag has been switched to false, we don't notify 924 // the listeners 925 if (!this.notify) { 926 return; 927 } 928 Object[] listeners = this.listenerList.getListenerList(); 929 for (int i = listeners.length - 2; i >= 0; i -= 2) { 930 if (listeners[i] == PlotChangeListener.class) { 931 ((PlotChangeListener) listeners[i + 1]).plotChanged(event); 932 } 933 } 934 } 935 936 /** 937 * Sends a {@link PlotChangeEvent} to all registered listeners. 938 * 939 * @since 1.0.10 940 */ 941 protected void fireChangeEvent() { 942 notifyListeners(new PlotChangeEvent(this)); 943 } 944 945 /** 946 * Draws the plot within the specified area. The anchor is a point on the 947 * chart that is specified externally (for instance, it may be the last 948 * point of the last mouse click performed by the user) - plots can use or 949 * ignore this value as they see fit. 950 * <br><br> 951 * Subclasses need to provide an implementation of this method, obviously. 952 * 953 * @param g2 the graphics device. 954 * @param area the plot area. 955 * @param anchor the anchor point ({@code null} permitted). 956 * @param parentState the parent state (if any, {@code null} permitted). 957 * @param info carries back plot rendering info. 958 */ 959 public abstract void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, 960 PlotState parentState, PlotRenderingInfo info); 961 962 /** 963 * Draws the plot background (the background color and/or image). 964 * <P> 965 * This method will be called during the chart drawing process and is 966 * declared public so that it can be accessed by the renderers used by 967 * certain subclasses. You shouldn't need to call this method directly. 968 * 969 * @param g2 the graphics device. 970 * @param area the area within which the plot should be drawn. 971 */ 972 public void drawBackground(Graphics2D g2, Rectangle2D area) { 973 // some subclasses override this method completely, so don't put 974 // anything here that *must* be done 975 fillBackground(g2, area); 976 drawBackgroundImage(g2, area); 977 } 978 979 /** 980 * Fills the specified area with the background paint. 981 * 982 * @param g2 the graphics device. 983 * @param area the area. 984 * 985 * @see #getBackgroundPaint() 986 * @see #getBackgroundAlpha() 987 * @see #fillBackground(Graphics2D, Rectangle2D, PlotOrientation) 988 */ 989 protected void fillBackground(Graphics2D g2, Rectangle2D area) { 990 fillBackground(g2, area, PlotOrientation.VERTICAL); 991 } 992 993 /** 994 * Fills the specified area with the background paint. If the background 995 * paint is an instance of {@code GradientPaint}, the gradient will 996 * run in the direction suggested by the plot's orientation. 997 * 998 * @param g2 the graphics target. 999 * @param area the plot area. 1000 * @param orientation the plot orientation ({@code null} not 1001 * permitted). 1002 * 1003 * @since 1.0.6 1004 */ 1005 protected void fillBackground(Graphics2D g2, Rectangle2D area, 1006 PlotOrientation orientation) { 1007 Args.nullNotPermitted(orientation, "orientation"); 1008 if (this.backgroundPaint == null) { 1009 return; 1010 } 1011 Paint p = this.backgroundPaint; 1012 if (p instanceof GradientPaint) { 1013 GradientPaint gp = (GradientPaint) p; 1014 if (orientation == PlotOrientation.VERTICAL) { 1015 p = new GradientPaint((float) area.getCenterX(), 1016 (float) area.getMaxY(), gp.getColor1(), 1017 (float) area.getCenterX(), (float) area.getMinY(), 1018 gp.getColor2()); 1019 } 1020 else if (orientation == PlotOrientation.HORIZONTAL) { 1021 p = new GradientPaint((float) area.getMinX(), 1022 (float) area.getCenterY(), gp.getColor1(), 1023 (float) area.getMaxX(), (float) area.getCenterY(), 1024 gp.getColor2()); 1025 } 1026 } 1027 Composite originalComposite = g2.getComposite(); 1028 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1029 this.backgroundAlpha)); 1030 g2.setPaint(p); 1031 g2.fill(area); 1032 g2.setComposite(originalComposite); 1033 } 1034 1035 /** 1036 * Draws the background image (if there is one) aligned within the 1037 * specified area. 1038 * 1039 * @param g2 the graphics device. 1040 * @param area the area. 1041 * 1042 * @see #getBackgroundImage() 1043 * @see #getBackgroundImageAlignment() 1044 * @see #getBackgroundImageAlpha() 1045 */ 1046 public void drawBackgroundImage(Graphics2D g2, Rectangle2D area) { 1047 if (this.backgroundImage == null) { 1048 return; // nothing to do 1049 } 1050 Composite savedComposite = g2.getComposite(); 1051 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1052 this.backgroundImageAlpha)); 1053 Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, 1054 this.backgroundImage.getWidth(null), 1055 this.backgroundImage.getHeight(null)); 1056 Align.align(dest, area, this.backgroundImageAlignment); 1057 Shape savedClip = g2.getClip(); 1058 g2.clip(area); 1059 g2.drawImage(this.backgroundImage, (int) dest.getX(), 1060 (int) dest.getY(), (int) dest.getWidth() + 1, 1061 (int) dest.getHeight() + 1, null); 1062 g2.setClip(savedClip); 1063 g2.setComposite(savedComposite); 1064 } 1065 1066 /** 1067 * Draws the plot outline. This method will be called during the chart 1068 * drawing process and is declared public so that it can be accessed by the 1069 * renderers used by certain subclasses. You shouldn't need to call this 1070 * method directly. 1071 * 1072 * @param g2 the graphics device. 1073 * @param area the area within which the plot should be drawn. 1074 */ 1075 public void drawOutline(Graphics2D g2, Rectangle2D area) { 1076 if (!this.outlineVisible) { 1077 return; 1078 } 1079 if ((this.outlineStroke != null) && (this.outlinePaint != null)) { 1080 g2.setStroke(this.outlineStroke); 1081 g2.setPaint(this.outlinePaint); 1082 Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL); 1083 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); 1084 g2.draw(area); 1085 g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved); 1086 } 1087 } 1088 1089 /** 1090 * Draws a message to state that there is no data to plot. 1091 * 1092 * @param g2 the graphics device. 1093 * @param area the area within which the plot should be drawn. 1094 */ 1095 protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) { 1096 Shape savedClip = g2.getClip(); 1097 g2.clip(area); 1098 String message = this.noDataMessage; 1099 if (message != null) { 1100 g2.setFont(this.noDataMessageFont); 1101 g2.setPaint(this.noDataMessagePaint); 1102 TextBlock block = TextUtils.createTextBlock( 1103 this.noDataMessage, this.noDataMessageFont, 1104 this.noDataMessagePaint, 0.9f * (float) area.getWidth(), 1105 new G2TextMeasurer(g2)); 1106 block.draw(g2, (float) area.getCenterX(), 1107 (float) area.getCenterY(), TextBlockAnchor.CENTER); 1108 } 1109 g2.setClip(savedClip); 1110 } 1111 1112 /** 1113 * Creates a plot entity that contains a reference to the plot and the 1114 * data area as shape. 1115 * 1116 * @param dataArea the data area used as hot spot for the entity. 1117 * @param plotState the plot rendering info containing a reference to the 1118 * EntityCollection. 1119 * @param toolTip the tool tip (defined in the respective Plot 1120 * subclass) ({@code null} permitted). 1121 * @param urlText the url (defined in the respective Plot subclass) 1122 * ({@code null} permitted). 1123 * 1124 * @since 1.0.13 1125 */ 1126 protected void createAndAddEntity(Rectangle2D dataArea, 1127 PlotRenderingInfo plotState, String toolTip, String urlText) { 1128 if (plotState != null && plotState.getOwner() != null) { 1129 EntityCollection e = plotState.getOwner().getEntityCollection(); 1130 if (e != null) { 1131 e.add(new PlotEntity(dataArea, this, toolTip, urlText)); 1132 } 1133 } 1134 } 1135 1136 /** 1137 * Handles a 'click' on the plot. Since the plot does not maintain any 1138 * information about where it has been drawn, the plot rendering info is 1139 * supplied as an argument so that the plot dimensions can be determined. 1140 * 1141 * @param x the x coordinate (in Java2D space). 1142 * @param y the y coordinate (in Java2D space). 1143 * @param info an object containing information about the dimensions of 1144 * the plot. 1145 */ 1146 public void handleClick(int x, int y, PlotRenderingInfo info) { 1147 // provides a 'no action' default 1148 } 1149 1150 /** 1151 * Performs a zoom on the plot. Subclasses should override if zooming is 1152 * appropriate for the type of plot. 1153 * 1154 * @param percent the zoom percentage. 1155 */ 1156 public void zoom(double percent) { 1157 // do nothing by default. 1158 } 1159 1160 /** 1161 * Receives notification of a change to an {@link Annotation} added to 1162 * this plot. 1163 * 1164 * @param event information about the event (not used here). 1165 * 1166 * @since 1.0.14 1167 */ 1168 @Override 1169 public void annotationChanged(AnnotationChangeEvent event) { 1170 fireChangeEvent(); 1171 } 1172 1173 /** 1174 * Receives notification of a change to one of the plot's axes. 1175 * 1176 * @param event information about the event (not used here). 1177 */ 1178 @Override 1179 public void axisChanged(AxisChangeEvent event) { 1180 fireChangeEvent(); 1181 } 1182 1183 /** 1184 * Receives notification of a change to the plot's dataset. 1185 * <P> 1186 * The plot reacts by passing on a plot change event to all registered 1187 * listeners. 1188 * 1189 * @param event information about the event (not used here). 1190 */ 1191 @Override 1192 public void datasetChanged(DatasetChangeEvent event) { 1193 PlotChangeEvent newEvent = new PlotChangeEvent(this); 1194 newEvent.setType(ChartChangeEventType.DATASET_UPDATED); 1195 notifyListeners(newEvent); 1196 } 1197 1198 /** 1199 * Receives notification of a change to a marker that is assigned to the 1200 * plot. 1201 * 1202 * @param event the event. 1203 * 1204 * @since 1.0.3 1205 */ 1206 @Override 1207 public void markerChanged(MarkerChangeEvent event) { 1208 fireChangeEvent(); 1209 } 1210 1211 /** 1212 * Adjusts the supplied x-value. 1213 * 1214 * @param x the x-value. 1215 * @param w1 width 1. 1216 * @param w2 width 2. 1217 * @param edge the edge (left or right). 1218 * 1219 * @return The adjusted x-value. 1220 */ 1221 protected double getRectX(double x, double w1, double w2, 1222 RectangleEdge edge) { 1223 1224 double result = x; 1225 if (edge == RectangleEdge.LEFT) { 1226 result = result + w1; 1227 } 1228 else if (edge == RectangleEdge.RIGHT) { 1229 result = result + w2; 1230 } 1231 return result; 1232 1233 } 1234 1235 /** 1236 * Adjusts the supplied y-value. 1237 * 1238 * @param y the x-value. 1239 * @param h1 height 1. 1240 * @param h2 height 2. 1241 * @param edge the edge (top or bottom). 1242 * 1243 * @return The adjusted y-value. 1244 */ 1245 protected double getRectY(double y, double h1, double h2, 1246 RectangleEdge edge) { 1247 1248 double result = y; 1249 if (edge == RectangleEdge.TOP) { 1250 result = result + h1; 1251 } 1252 else if (edge == RectangleEdge.BOTTOM) { 1253 result = result + h2; 1254 } 1255 return result; 1256 1257 } 1258 1259 /** 1260 * Tests this plot for equality with another object. 1261 * 1262 * @param obj the object ({@code null} permitted). 1263 * 1264 * @return {@code true} or {@code false}. 1265 */ 1266 @Override 1267 public boolean equals(Object obj) { 1268 if (obj == this) { 1269 return true; 1270 } 1271 if (!(obj instanceof Plot)) { 1272 return false; 1273 } 1274 Plot that = (Plot) obj; 1275 if (!ObjectUtils.equal(this.noDataMessage, that.noDataMessage)) { 1276 return false; 1277 } 1278 if (!ObjectUtils.equal( 1279 this.noDataMessageFont, that.noDataMessageFont 1280 )) { 1281 return false; 1282 } 1283 if (!PaintUtils.equal(this.noDataMessagePaint, 1284 that.noDataMessagePaint)) { 1285 return false; 1286 } 1287 if (!ObjectUtils.equal(this.insets, that.insets)) { 1288 return false; 1289 } 1290 if (this.outlineVisible != that.outlineVisible) { 1291 return false; 1292 } 1293 if (!ObjectUtils.equal(this.outlineStroke, that.outlineStroke)) { 1294 return false; 1295 } 1296 if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { 1297 return false; 1298 } 1299 if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { 1300 return false; 1301 } 1302 if (!ObjectUtils.equal(this.backgroundImage, 1303 that.backgroundImage)) { 1304 return false; 1305 } 1306 if (this.backgroundImageAlignment != that.backgroundImageAlignment) { 1307 return false; 1308 } 1309 if (this.backgroundImageAlpha != that.backgroundImageAlpha) { 1310 return false; 1311 } 1312 if (this.foregroundAlpha != that.foregroundAlpha) { 1313 return false; 1314 } 1315 if (this.backgroundAlpha != that.backgroundAlpha) { 1316 return false; 1317 } 1318 if (!this.drawingSupplier.equals(that.drawingSupplier)) { 1319 return false; 1320 } 1321 if (this.notify != that.notify) { 1322 return false; 1323 } 1324 return true; 1325 } 1326 1327 /** 1328 * Creates a clone of the plot. 1329 * 1330 * @return A clone. 1331 * 1332 * @throws CloneNotSupportedException if some component of the plot does not 1333 * support cloning. 1334 */ 1335 @Override 1336 public Object clone() throws CloneNotSupportedException { 1337 1338 Plot clone = (Plot) super.clone(); 1339 // private Plot parent <-- don't clone the parent plot, but take care 1340 // childs in combined plots instead 1341 if (this.datasetGroup != null) { 1342 clone.datasetGroup 1343 = (DatasetGroup) ObjectUtils.clone(this.datasetGroup); 1344 } 1345 clone.drawingSupplier 1346 = (DrawingSupplier) ObjectUtils.clone(this.drawingSupplier); 1347 clone.listenerList = new EventListenerList(); 1348 return clone; 1349 1350 } 1351 1352 /** 1353 * Provides serialization support. 1354 * 1355 * @param stream the output stream. 1356 * 1357 * @throws IOException if there is an I/O error. 1358 */ 1359 private void writeObject(ObjectOutputStream stream) throws IOException { 1360 stream.defaultWriteObject(); 1361 SerialUtils.writePaint(this.noDataMessagePaint, stream); 1362 SerialUtils.writeStroke(this.outlineStroke, stream); 1363 SerialUtils.writePaint(this.outlinePaint, stream); 1364 // backgroundImage 1365 SerialUtils.writePaint(this.backgroundPaint, stream); 1366 } 1367 1368 /** 1369 * Provides serialization support. 1370 * 1371 * @param stream the input stream. 1372 * 1373 * @throws IOException if there is an I/O error. 1374 * @throws ClassNotFoundException if there is a classpath problem. 1375 */ 1376 private void readObject(ObjectInputStream stream) 1377 throws IOException, ClassNotFoundException { 1378 stream.defaultReadObject(); 1379 this.noDataMessagePaint = SerialUtils.readPaint(stream); 1380 this.outlineStroke = SerialUtils.readStroke(stream); 1381 this.outlinePaint = SerialUtils.readPaint(stream); 1382 // backgroundImage 1383 this.backgroundPaint = SerialUtils.readPaint(stream); 1384 1385 this.listenerList = new EventListenerList(); 1386 1387 } 1388 1389 /** 1390 * Resolves a domain axis location for a given plot orientation. 1391 * 1392 * @param location the location ({@code null} not permitted). 1393 * @param orientation the orientation ({@code null} not permitted). 1394 * 1395 * @return The edge (never {@code null}). 1396 */ 1397 public static RectangleEdge resolveDomainAxisLocation( 1398 AxisLocation location, PlotOrientation orientation) { 1399 1400 Args.nullNotPermitted(location, "location"); 1401 Args.nullNotPermitted(orientation, "orientation"); 1402 1403 RectangleEdge result = null; 1404 if (location == AxisLocation.TOP_OR_RIGHT) { 1405 if (orientation == PlotOrientation.HORIZONTAL) { 1406 result = RectangleEdge.RIGHT; 1407 } 1408 else if (orientation == PlotOrientation.VERTICAL) { 1409 result = RectangleEdge.TOP; 1410 } 1411 } 1412 else if (location == AxisLocation.TOP_OR_LEFT) { 1413 if (orientation == PlotOrientation.HORIZONTAL) { 1414 result = RectangleEdge.LEFT; 1415 } 1416 else if (orientation == PlotOrientation.VERTICAL) { 1417 result = RectangleEdge.TOP; 1418 } 1419 } 1420 else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1421 if (orientation == PlotOrientation.HORIZONTAL) { 1422 result = RectangleEdge.RIGHT; 1423 } 1424 else if (orientation == PlotOrientation.VERTICAL) { 1425 result = RectangleEdge.BOTTOM; 1426 } 1427 } 1428 else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1429 if (orientation == PlotOrientation.HORIZONTAL) { 1430 result = RectangleEdge.LEFT; 1431 } 1432 else if (orientation == PlotOrientation.VERTICAL) { 1433 result = RectangleEdge.BOTTOM; 1434 } 1435 } 1436 // the above should cover all the options... 1437 if (result == null) { 1438 throw new IllegalStateException("resolveDomainAxisLocation()"); 1439 } 1440 return result; 1441 1442 } 1443 1444 /** 1445 * Resolves a range axis location for a given plot orientation. 1446 * 1447 * @param location the location ({@code null} not permitted). 1448 * @param orientation the orientation ({@code null} not permitted). 1449 * 1450 * @return The edge (never {@code null}). 1451 */ 1452 public static RectangleEdge resolveRangeAxisLocation( 1453 AxisLocation location, PlotOrientation orientation) { 1454 1455 Args.nullNotPermitted(location, "location"); 1456 Args.nullNotPermitted(orientation, "orientation"); 1457 1458 RectangleEdge result = null; 1459 if (location == AxisLocation.TOP_OR_RIGHT) { 1460 if (orientation == PlotOrientation.HORIZONTAL) { 1461 result = RectangleEdge.TOP; 1462 } 1463 else if (orientation == PlotOrientation.VERTICAL) { 1464 result = RectangleEdge.RIGHT; 1465 } 1466 } 1467 else if (location == AxisLocation.TOP_OR_LEFT) { 1468 if (orientation == PlotOrientation.HORIZONTAL) { 1469 result = RectangleEdge.TOP; 1470 } 1471 else if (orientation == PlotOrientation.VERTICAL) { 1472 result = RectangleEdge.LEFT; 1473 } 1474 } 1475 else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1476 if (orientation == PlotOrientation.HORIZONTAL) { 1477 result = RectangleEdge.BOTTOM; 1478 } 1479 else if (orientation == PlotOrientation.VERTICAL) { 1480 result = RectangleEdge.RIGHT; 1481 } 1482 } 1483 else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1484 if (orientation == PlotOrientation.HORIZONTAL) { 1485 result = RectangleEdge.BOTTOM; 1486 } 1487 else if (orientation == PlotOrientation.VERTICAL) { 1488 result = RectangleEdge.LEFT; 1489 } 1490 } 1491 1492 // the above should cover all the options... 1493 if (result == null) { 1494 throw new IllegalStateException("resolveRangeAxisLocation()"); 1495 } 1496 return result; 1497 1498 } 1499 1500}