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