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