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