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