Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * --------- 28: * Plot.java 29: * --------- 30: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Sylvain Vieujot; 34: * Jeremy Bowman; 35: * Andreas Schneider; 36: * Gideon Krause; 37: * Nicolas Brodu; 38: * Michal Krause; 39: * 40: * $Id: Plot.java,v 1.18.2.6 2007/01/11 11:32:57 mungady Exp $ 41: * 42: * Changes (from 21-Jun-2001) 43: * -------------------------- 44: * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG); 45: * 18-Sep-2001 : Updated header info and fixed DOS encoding problem (DG); 46: * 19-Oct-2001 : Moved series paint and stroke methods from JFreeChart 47: * class (DG); 48: * 23-Oct-2001 : Created renderer for LinePlot class (DG); 49: * 07-Nov-2001 : Changed type names for ChartChangeEvent (DG); 50: * Tidied up some Javadoc comments (DG); 51: * 13-Nov-2001 : Changes to allow for null axes on plots such as PiePlot (DG); 52: * Added plot/axis compatibility checks (DG); 53: * 12-Dec-2001 : Changed constructors to protected, and removed unnecessary 54: * 'throws' clauses (DG); 55: * 13-Dec-2001 : Added tooltips (DG); 56: * 22-Jan-2002 : Added handleClick() method, as part of implementation for 57: * crosshairs (DG); 58: * Moved tooltips reference into ChartInfo class (DG); 59: * 23-Jan-2002 : Added test for null axes in chartChanged() method, thanks 60: * to Barry Evans for the bug report (number 506979 on 61: * SourceForge) (DG); 62: * Added a zoom() method (DG); 63: * 05-Feb-2002 : Updated setBackgroundPaint(), setOutlineStroke() and 64: * setOutlinePaint() to better handle null values, as suggested 65: * by Sylvain Vieujot (DG); 66: * 06-Feb-2002 : Added background image, plus alpha transparency for background 67: * and foreground (DG); 68: * 06-Mar-2002 : Added AxisConstants interface (DG); 69: * 26-Mar-2002 : Changed zoom method from empty to abstract (DG); 70: * 23-Apr-2002 : Moved dataset from JFreeChart class (DG); 71: * 11-May-2002 : Added ShapeFactory interface for getShape() methods, 72: * contributed by Jeremy Bowman (DG); 73: * 28-May-2002 : Fixed bug in setSeriesPaint(int, Paint) for subplots (AS); 74: * 25-Jun-2002 : Removed redundant imports (DG); 75: * 30-Jul-2002 : Added 'no data' message for charts with null or empty 76: * datasets (DG); 77: * 21-Aug-2002 : Added code to extend series array if necessary (refer to 78: * SourceForge bug id 594547 for details) (DG); 79: * 17-Sep-2002 : Fixed bug in getSeriesOutlineStroke() method, reported by 80: * Andreas Schroeder (DG); 81: * 23-Sep-2002 : Added getLegendItems() abstract method (DG); 82: * 24-Sep-2002 : Removed firstSeriesIndex, subplots now use their own paint 83: * settings, there is a new mechanism for the legend to collect 84: * the legend items (DG); 85: * 27-Sep-2002 : Added dataset group (DG); 86: * 14-Oct-2002 : Moved listener storage into EventListenerList. Changed some 87: * abstract methods to empty implementations (DG); 88: * 28-Oct-2002 : Added a getBackgroundImage() method (DG); 89: * 21-Nov-2002 : Added a plot index for identifying subplots in combined and 90: * overlaid charts (DG); 91: * 22-Nov-2002 : Changed all attributes from 'protected' to 'private'. Added 92: * dataAreaRatio attribute from David M O'Donnell's code (DG); 93: * 09-Jan-2003 : Integrated fix for plot border contributed by Gideon 94: * Krause (DG); 95: * 17-Jan-2003 : Moved to com.jrefinery.chart.plot (DG); 96: * 23-Jan-2003 : Removed one constructor (DG); 97: * 26-Mar-2003 : Implemented Serializable (DG); 98: * 14-Jul-2003 : Moved the dataset and secondaryDataset attributes to the 99: * 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: }