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