Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2006, 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-2006, 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.4 2006/06/30 13:04:52 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.0 --------------------------------------------- 117: * 30-Jun-2006 : Added background image alpha - see bug report 1514904 (DG); 118: * 119: */ 120: 121: package org.jfree.chart.plot; 122: 123: import java.awt.AlphaComposite; 124: import java.awt.BasicStroke; 125: import java.awt.Color; 126: import java.awt.Composite; 127: import java.awt.Font; 128: import java.awt.Graphics2D; 129: import java.awt.Image; 130: import java.awt.Paint; 131: import java.awt.Shape; 132: import java.awt.Stroke; 133: import java.awt.geom.Ellipse2D; 134: import java.awt.geom.Point2D; 135: import java.awt.geom.Rectangle2D; 136: import java.io.IOException; 137: import java.io.ObjectInputStream; 138: import java.io.ObjectOutputStream; 139: import java.io.Serializable; 140: 141: import javax.swing.event.EventListenerList; 142: 143: import org.jfree.chart.LegendItemCollection; 144: import org.jfree.chart.LegendItemSource; 145: import org.jfree.chart.axis.AxisLocation; 146: import org.jfree.chart.event.AxisChangeEvent; 147: import org.jfree.chart.event.AxisChangeListener; 148: import org.jfree.chart.event.ChartChangeEventType; 149: import org.jfree.chart.event.PlotChangeEvent; 150: import org.jfree.chart.event.PlotChangeListener; 151: import org.jfree.data.general.DatasetChangeEvent; 152: import org.jfree.data.general.DatasetChangeListener; 153: import org.jfree.data.general.DatasetGroup; 154: import org.jfree.io.SerialUtilities; 155: import org.jfree.text.G2TextMeasurer; 156: import org.jfree.text.TextBlock; 157: import org.jfree.text.TextBlockAnchor; 158: import org.jfree.text.TextUtilities; 159: import org.jfree.ui.Align; 160: import org.jfree.ui.RectangleEdge; 161: import org.jfree.ui.RectangleInsets; 162: import org.jfree.util.ObjectUtilities; 163: import org.jfree.util.PaintUtilities; 164: import org.jfree.util.PublicCloneable; 165: 166: /** 167: * The base class for all plots in JFreeChart. The 168: * {@link org.jfree.chart.JFreeChart} class delegates the drawing of axes and 169: * data to the plot. This base class provides facilities common to most plot 170: * types. 171: */ 172: public abstract class Plot implements AxisChangeListener, 173: DatasetChangeListener, 174: LegendItemSource, 175: PublicCloneable, 176: Cloneable, 177: Serializable { 178: 179: /** For serialization. */ 180: private static final long serialVersionUID = -8831571430103671324L; 181: 182: /** Useful constant representing zero. */ 183: public static final Number ZERO = new Integer(0); 184: 185: /** The default insets. */ 186: public static final RectangleInsets DEFAULT_INSETS 187: = new RectangleInsets(4.0, 8.0, 4.0, 8.0); 188: 189: /** The default outline stroke. */ 190: public static final Stroke DEFAULT_OUTLINE_STROKE = new BasicStroke(0.5f); 191: 192: /** The default outline color. */ 193: public static final Paint DEFAULT_OUTLINE_PAINT = Color.gray; 194: 195: /** The default foreground alpha transparency. */ 196: public static final float DEFAULT_FOREGROUND_ALPHA = 1.0f; 197: 198: /** The default background alpha transparency. */ 199: public static final float DEFAULT_BACKGROUND_ALPHA = 1.0f; 200: 201: /** The default background color. */ 202: public static final Paint DEFAULT_BACKGROUND_PAINT = Color.white; 203: 204: /** The minimum width at which the plot should be drawn. */ 205: public static final int MINIMUM_WIDTH_TO_DRAW = 10; 206: 207: /** The minimum height at which the plot should be drawn. */ 208: public static final int MINIMUM_HEIGHT_TO_DRAW = 10; 209: 210: /** A default box shape for legend items. */ 211: public static final Shape DEFAULT_LEGEND_ITEM_BOX 212: = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0); 213: 214: /** A default circle shape for legend items. */ 215: public static final Shape DEFAULT_LEGEND_ITEM_CIRCLE 216: = new Ellipse2D.Double(-4.0, -4.0, 8.0, 8.0); 217: 218: /** The parent plot (<code>null</code> if this is the root plot). */ 219: private Plot parent; 220: 221: /** The dataset group (to be used for thread synchronisation). */ 222: private DatasetGroup datasetGroup; 223: 224: /** The message to display if no data is available. */ 225: private String noDataMessage; 226: 227: /** The font used to display the 'no data' message. */ 228: private Font noDataMessageFont; 229: 230: /** The paint used to draw the 'no data' message. */ 231: private transient Paint noDataMessagePaint; 232: 233: /** Amount of blank space around the plot area. */ 234: private RectangleInsets insets; 235: 236: /** The Stroke used to draw an outline around the plot. */ 237: private transient Stroke outlineStroke; 238: 239: /** The Paint used to draw an outline around the plot. */ 240: private transient Paint outlinePaint; 241: 242: /** An optional color used to fill the plot background. */ 243: private transient Paint backgroundPaint; 244: 245: /** An optional image for the plot background. */ 246: private transient Image backgroundImage; // not currently serialized 247: 248: /** The alignment for the background image. */ 249: private int backgroundImageAlignment = Align.FIT; 250: 251: /** The alpha value used to draw the background image. */ 252: private float backgroundImageAlpha = 0.5f; 253: 254: /** The alpha-transparency for the plot. */ 255: private float foregroundAlpha; 256: 257: /** The alpha transparency for the background paint. */ 258: private float backgroundAlpha; 259: 260: /** The drawing supplier. */ 261: private DrawingSupplier drawingSupplier; 262: 263: /** Storage for registered change listeners. */ 264: private transient EventListenerList listenerList; 265: 266: /** 267: * Creates a new plot. 268: */ 269: protected Plot() { 270: 271: this.parent = null; 272: this.insets = DEFAULT_INSETS; 273: this.backgroundPaint = DEFAULT_BACKGROUND_PAINT; 274: this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA; 275: this.backgroundImage = null; 276: this.outlineStroke = DEFAULT_OUTLINE_STROKE; 277: this.outlinePaint = DEFAULT_OUTLINE_PAINT; 278: this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA; 279: 280: this.noDataMessage = null; 281: this.noDataMessageFont = new Font("SansSerif", Font.PLAIN, 12); 282: this.noDataMessagePaint = Color.black; 283: 284: this.drawingSupplier = new DefaultDrawingSupplier(); 285: 286: this.listenerList = new EventListenerList(); 287: 288: } 289: 290: /** 291: * Returns the dataset group for the plot (not currently used). 292: * 293: * @return The dataset group. 294: */ 295: public DatasetGroup getDatasetGroup() { 296: return this.datasetGroup; 297: } 298: 299: /** 300: * Sets the dataset group (not currently used). 301: * 302: * @param group the dataset group (<code>null</code> permitted). 303: */ 304: protected void setDatasetGroup(DatasetGroup group) { 305: this.datasetGroup = group; 306: } 307: 308: /** 309: * Returns the string that is displayed when the dataset is empty or 310: * <code>null</code>. 311: * 312: * @return The 'no data' message (<code>null</code> possible). 313: */ 314: public String getNoDataMessage() { 315: return this.noDataMessage; 316: } 317: 318: /** 319: * Sets the message that is displayed when the dataset is empty or null. 320: * 321: * @param message the message (null permitted). 322: */ 323: public void setNoDataMessage(String message) { 324: this.noDataMessage = message; 325: } 326: 327: /** 328: * Returns the font used to display the 'no data' message. 329: * 330: * @return The font. 331: */ 332: public Font getNoDataMessageFont() { 333: return this.noDataMessageFont; 334: } 335: 336: /** 337: * Sets the font used to display the 'no data' message. 338: * 339: * @param font the font. 340: */ 341: public void setNoDataMessageFont(Font font) { 342: this.noDataMessageFont = font; 343: } 344: 345: /** 346: * Returns the paint used to display the 'no data' message. 347: * 348: * @return The paint. 349: */ 350: public Paint getNoDataMessagePaint() { 351: return this.noDataMessagePaint; 352: } 353: 354: /** 355: * Sets the paint used to display the 'no data' message. 356: * 357: * @param paint the paint. 358: */ 359: public void setNoDataMessagePaint(Paint paint) { 360: this.noDataMessagePaint = paint; 361: } 362: 363: /** 364: * Returns a short string describing the plot type. 365: * <P> 366: * Note: this gets used in the chart property editing user interface, 367: * but there needs to be a better mechanism for identifying the plot type. 368: * 369: * @return A short string describing the plot type. 370: */ 371: public abstract String getPlotType(); 372: 373: /** 374: * Returns the parent plot (or <code>null</code> if this plot is not part 375: * of a combined plot). 376: * 377: * @return The parent plot. 378: */ 379: public Plot getParent() { 380: return this.parent; 381: } 382: 383: /** 384: * Sets the parent plot. 385: * 386: * @param parent the parent plot. 387: */ 388: public void setParent(Plot parent) { 389: this.parent = parent; 390: } 391: 392: /** 393: * Returns the root plot. 394: * 395: * @return The root plot. 396: */ 397: public Plot getRootPlot() { 398: 399: Plot p = getParent(); 400: if (p == null) { 401: return this; 402: } 403: else { 404: return p.getRootPlot(); 405: } 406: 407: } 408: 409: /** 410: * Returns true if this plot is part of a combined plot structure. 411: * 412: * @return <code>true</code> if this plot is part of a combined plot 413: * structure. 414: */ 415: public boolean isSubplot() { 416: return (getParent() != null); 417: } 418: 419: /** 420: * Returns the insets for the plot area. 421: * 422: * @return The insets (never <code>null</code>). 423: */ 424: public RectangleInsets getInsets() { 425: return this.insets; 426: } 427: 428: /** 429: * Sets the insets for the plot and sends a {@link PlotChangeEvent} to 430: * all registered listeners. 431: * 432: * @param insets the new insets (<code>null</code> not permitted). 433: */ 434: public void setInsets(RectangleInsets insets) { 435: setInsets(insets, true); 436: } 437: 438: /** 439: * Sets the insets for the plot and, if requested, and sends a 440: * {@link PlotChangeEvent} to all registered listeners. 441: * 442: * @param insets the new insets (<code>null</code> not permitted). 443: * @param notify a flag that controls whether the registered listeners are 444: * notified. 445: */ 446: public void setInsets(RectangleInsets insets, boolean notify) { 447: if (insets == null) { 448: throw new IllegalArgumentException("Null 'insets' argument."); 449: } 450: if (!this.insets.equals(insets)) { 451: this.insets = insets; 452: if (notify) { 453: notifyListeners(new PlotChangeEvent(this)); 454: } 455: } 456: 457: } 458: 459: /** 460: * Returns the background color of the plot area. 461: * 462: * @return The paint (possibly <code>null</code>). 463: */ 464: public Paint getBackgroundPaint() { 465: return this.backgroundPaint; 466: } 467: 468: /** 469: * Sets the background color of the plot area and sends a 470: * {@link PlotChangeEvent} to all registered listeners. 471: * 472: * @param paint the paint (<code>null</code> permitted). 473: */ 474: public void setBackgroundPaint(Paint paint) { 475: 476: if (paint == null) { 477: if (this.backgroundPaint != null) { 478: this.backgroundPaint = null; 479: notifyListeners(new PlotChangeEvent(this)); 480: } 481: } 482: else { 483: if (this.backgroundPaint != null) { 484: if (this.backgroundPaint.equals(paint)) { 485: return; // nothing to do 486: } 487: } 488: this.backgroundPaint = paint; 489: notifyListeners(new PlotChangeEvent(this)); 490: } 491: 492: } 493: 494: /** 495: * Returns the alpha transparency of the plot area background. 496: * 497: * @return The alpha transparency. 498: */ 499: public float getBackgroundAlpha() { 500: return this.backgroundAlpha; 501: } 502: 503: /** 504: * Sets the alpha transparency of the plot area background, and notifies 505: * registered listeners that the plot has been modified. 506: * 507: * @param alpha the new alpha value. 508: */ 509: public void setBackgroundAlpha(float alpha) { 510: 511: if (this.backgroundAlpha != alpha) { 512: this.backgroundAlpha = alpha; 513: notifyListeners(new PlotChangeEvent(this)); 514: } 515: 516: } 517: 518: /** 519: * Returns the drawing supplier for the plot. 520: * 521: * @return The drawing supplier (possibly <code>null</code>). 522: */ 523: public DrawingSupplier getDrawingSupplier() { 524: DrawingSupplier result = null; 525: Plot p = getParent(); 526: if (p != null) { 527: result = p.getDrawingSupplier(); 528: } 529: else { 530: result = this.drawingSupplier; 531: } 532: return result; 533: } 534: 535: /** 536: * Sets the drawing supplier for the plot. The drawing supplier is 537: * responsible for supplying a limitless (possibly repeating) sequence of 538: * <code>Paint</code>, <code>Stroke</code> and <code>Shape</code> objects 539: * that the plot's renderer(s) can use to populate its (their) tables. 540: * 541: * @param supplier the new supplier. 542: */ 543: public void setDrawingSupplier(DrawingSupplier supplier) { 544: this.drawingSupplier = supplier; 545: notifyListeners(new PlotChangeEvent(this)); 546: } 547: 548: /** 549: * Returns the background image that is used to fill the plot's background 550: * area. 551: * 552: * @return The image (possibly <code>null</code>). 553: */ 554: public Image getBackgroundImage() { 555: return this.backgroundImage; 556: } 557: 558: /** 559: * Sets the background image for the plot. 560: * 561: * @param image the image (<code>null</code> permitted). 562: */ 563: public void setBackgroundImage(Image image) { 564: this.backgroundImage = image; 565: notifyListeners(new PlotChangeEvent(this)); 566: } 567: 568: /** 569: * Returns the background image alignment. Alignment constants are defined 570: * in the <code>org.jfree.ui.Align</code> class in the JCommon class 571: * library. 572: * 573: * @return The alignment. 574: */ 575: public int getBackgroundImageAlignment() { 576: return this.backgroundImageAlignment; 577: } 578: 579: /** 580: * Sets the alignment for the background image and sends a 581: * {@link PlotChangeEvent} to all registered listeners. Alignment options 582: * are defined by the {@link org.jfree.ui.Align} class in the JCommon 583: * class library. 584: * 585: * @param alignment the alignment. 586: */ 587: public void setBackgroundImageAlignment(int alignment) { 588: if (this.backgroundImageAlignment != alignment) { 589: this.backgroundImageAlignment = alignment; 590: notifyListeners(new PlotChangeEvent(this)); 591: } 592: } 593: 594: /** 595: * Returns the alpha transparency used to draw the background image. This 596: * is a value in the range 0.0f to 1.0f, where 0.0f is fully transparent 597: * and 1.0f is fully opaque. 598: * 599: * @return The alpha transparency. 600: * 601: * @see #setBackgroundImageAlpha(float) 602: */ 603: public float getBackgroundImageAlpha() { 604: return this.backgroundImageAlpha; 605: } 606: 607: /** 608: * Sets the alpha transparency used when drawing the background image. 609: * 610: * @param alpha the alpha transparency (in the range 0.0f to 1.0f, where 611: * 0.0f is fully transparent, and 1.0f is fully opaque). 612: * 613: * @throws IllegalArgumentException if <code>alpha</code> is not within 614: * the specified range. 615: * 616: * @see #getBackgroundImageAlpha() 617: */ 618: public void setBackgroundImageAlpha(float alpha) 619: { 620: if (alpha < 0.0f || alpha > 1.0f) 621: throw new IllegalArgumentException( 622: "The 'alpha' value must be in the range 0.0f to 1.0f."); 623: if (this.backgroundImageAlpha != alpha) { 624: this.backgroundImageAlpha = alpha; 625: this.notifyListeners(new PlotChangeEvent(this)); 626: } 627: } 628: 629: /** 630: * Returns the stroke used to outline the plot area. 631: * 632: * @return The stroke (possibly <code>null</code>). 633: */ 634: public Stroke getOutlineStroke() { 635: return this.outlineStroke; 636: } 637: 638: /** 639: * Sets the stroke used to outline the plot area and sends a 640: * {@link PlotChangeEvent} to all registered listeners. If you set this 641: * attribute to <code>null<.code>, no outline will be drawn. 642: * 643: * @param stroke the stroke (<code>null</code> permitted). 644: */ 645: public void setOutlineStroke(Stroke stroke) { 646: 647: if (stroke == null) { 648: if (this.outlineStroke != null) { 649: this.outlineStroke = null; 650: notifyListeners(new PlotChangeEvent(this)); 651: } 652: } 653: else { 654: if (this.outlineStroke != null) { 655: if (this.outlineStroke.equals(stroke)) { 656: return; // nothing to do 657: } 658: } 659: this.outlineStroke = stroke; 660: notifyListeners(new PlotChangeEvent(this)); 661: } 662: 663: } 664: 665: /** 666: * Returns the color used to draw the outline of the plot area. 667: * 668: * @return The color (possibly <code>null<code>). 669: */ 670: public Paint getOutlinePaint() { 671: return this.outlinePaint; 672: } 673: 674: /** 675: * Sets the paint used to draw the outline of the plot area and sends a 676: * {@link PlotChangeEvent} to all registered listeners. If you set this 677: * attribute to <code>null</code>, no outline will be drawn. 678: * 679: * @param paint the paint (<code>null</code> permitted). 680: */ 681: public void setOutlinePaint(Paint paint) { 682: 683: if (paint == null) { 684: if (this.outlinePaint != null) { 685: this.outlinePaint = null; 686: notifyListeners(new PlotChangeEvent(this)); 687: } 688: } 689: else { 690: if (this.outlinePaint != null) { 691: if (this.outlinePaint.equals(paint)) { 692: return; // nothing to do 693: } 694: } 695: this.outlinePaint = paint; 696: notifyListeners(new PlotChangeEvent(this)); 697: } 698: 699: } 700: 701: /** 702: * Returns the alpha-transparency for the plot foreground. 703: * 704: * @return The alpha-transparency. 705: */ 706: public float getForegroundAlpha() { 707: return this.foregroundAlpha; 708: } 709: 710: /** 711: * Sets the alpha-transparency for the plot. 712: * 713: * @param alpha the new alpha transparency. 714: */ 715: public void setForegroundAlpha(float alpha) { 716: 717: if (this.foregroundAlpha != alpha) { 718: this.foregroundAlpha = alpha; 719: notifyListeners(new PlotChangeEvent(this)); 720: } 721: 722: } 723: 724: /** 725: * Returns the legend items for the plot. By default, this method returns 726: * <code>null</code>. Subclasses should override to return a 727: * {@link LegendItemCollection}. 728: * 729: * @return The legend items for the plot (possibly <code>null</code>). 730: */ 731: public LegendItemCollection getLegendItems() { 732: return null; 733: } 734: 735: /** 736: * Registers an object for notification of changes to the plot. 737: * 738: * @param listener the object to be registered. 739: */ 740: public void addChangeListener(PlotChangeListener listener) { 741: this.listenerList.add(PlotChangeListener.class, listener); 742: } 743: 744: /** 745: * Unregisters an object for notification of changes to the plot. 746: * 747: * @param listener the object to be unregistered. 748: */ 749: public void removeChangeListener(PlotChangeListener listener) { 750: this.listenerList.remove(PlotChangeListener.class, listener); 751: } 752: 753: /** 754: * Notifies all registered listeners that the plot has been modified. 755: * 756: * @param event information about the change event. 757: */ 758: public void notifyListeners(PlotChangeEvent event) { 759: 760: Object[] listeners = this.listenerList.getListenerList(); 761: for (int i = listeners.length - 2; i >= 0; i -= 2) { 762: if (listeners[i] == PlotChangeListener.class) { 763: ((PlotChangeListener) listeners[i + 1]).plotChanged(event); 764: } 765: } 766: 767: } 768: 769: /** 770: * Draws the plot within the specified area. The anchor is a point on the 771: * chart that is specified externally (for instance, it may be the last 772: * point of the last mouse click performed by the user) - plots can use or 773: * ignore this value as they see fit. 774: * <br><br> 775: * Subclasses need to provide an implementation of this method, obviously. 776: * 777: * @param g2 the graphics device. 778: * @param area the plot area. 779: * @param anchor the anchor point (<code>null</code> permitted). 780: * @param parentState the parent state (if any). 781: * @param info carries back plot rendering info. 782: */ 783: public abstract void draw(Graphics2D g2, 784: Rectangle2D area, 785: Point2D anchor, 786: PlotState parentState, 787: PlotRenderingInfo info); 788: 789: /** 790: * Draws the plot background (the background color and/or image). 791: * <P> 792: * This method will be called during the chart drawing process and is 793: * declared public so that it can be accessed by the renderers used by 794: * certain subclasses. You shouldn't need to call this method directly. 795: * 796: * @param g2 the graphics device. 797: * @param area the area within which the plot should be drawn. 798: */ 799: public void drawBackground(Graphics2D g2, Rectangle2D area) { 800: fillBackground(g2, area); 801: drawBackgroundImage(g2, area); 802: } 803: 804: /** 805: * Fills the specified area with the background paint. 806: * 807: * @param g2 the graphics device. 808: * @param area the area. 809: */ 810: protected void fillBackground(Graphics2D g2, Rectangle2D area) { 811: if (this.backgroundPaint != null) { 812: Composite originalComposite = g2.getComposite(); 813: g2.setComposite( 814: AlphaComposite.getInstance( 815: AlphaComposite.SRC_OVER, this.backgroundAlpha 816: ) 817: ); 818: g2.setPaint(this.backgroundPaint); 819: g2.fill(area); 820: g2.setComposite(originalComposite); 821: } 822: } 823: 824: /** 825: * Draws the background image (if there is one) aligned within the 826: * specified area. 827: * 828: * @param g2 the graphics device. 829: * @param area the area. 830: * 831: * @see #getBackgroundImage() 832: * @see #getBackgroundImageAlignment() 833: * @see #getBackgroundImageAlpha() 834: */ 835: protected void drawBackgroundImage(Graphics2D g2, Rectangle2D area) { 836: if (this.backgroundImage != null) { 837: Composite originalComposite = g2.getComposite(); 838: g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 839: this.backgroundImageAlpha)); 840: Rectangle2D dest = new Rectangle2D.Double(0.0, 0.0, 841: this.backgroundImage.getWidth(null), 842: this.backgroundImage.getHeight(null)); 843: Align.align(dest, area, this.backgroundImageAlignment); 844: g2.drawImage(this.backgroundImage, (int) dest.getX(), 845: (int) dest.getY(), (int) dest.getWidth() + 1, 846: (int) dest.getHeight() + 1, null); 847: g2.setComposite(originalComposite); 848: } 849: } 850: 851: /** 852: * Draws the plot outline. This method will be called during the chart 853: * drawing process and is declared public so that it can be accessed by the 854: * renderers used by certain subclasses. You shouldn't need to call this 855: * method directly. 856: * 857: * @param g2 the graphics device. 858: * @param area the area within which the plot should be drawn. 859: */ 860: public void drawOutline(Graphics2D g2, Rectangle2D area) { 861: if ((this.outlineStroke != null) && (this.outlinePaint != null)) { 862: g2.setStroke(this.outlineStroke); 863: g2.setPaint(this.outlinePaint); 864: g2.draw(area); 865: } 866: } 867: 868: /** 869: * Draws a message to state that there is no data to plot. 870: * 871: * @param g2 the graphics device. 872: * @param area the area within which the plot should be drawn. 873: */ 874: protected void drawNoDataMessage(Graphics2D g2, Rectangle2D area) { 875: 876: Shape savedClip = g2.getClip(); 877: g2.clip(area); 878: String message = this.noDataMessage; 879: if (message != null) { 880: g2.setFont(this.noDataMessageFont); 881: g2.setPaint(this.noDataMessagePaint); 882: TextBlock block = TextUtilities.createTextBlock( 883: this.noDataMessage, this.noDataMessageFont, 884: this.noDataMessagePaint, 885: 0.9f * (float) area.getWidth(), new G2TextMeasurer(g2) 886: ); 887: block.draw( 888: g2, (float) area.getCenterX(), (float) area.getCenterY(), 889: TextBlockAnchor.CENTER 890: ); 891: } 892: g2.setClip(savedClip); 893: 894: } 895: 896: /** 897: * Handles a 'click' on the plot. Since the plot does not maintain any 898: * information about where it has been drawn, the plot rendering info is 899: * supplied as an argument. 900: * 901: * @param x the x coordinate (in Java2D space). 902: * @param y the y coordinate (in Java2D space). 903: * @param info an object containing information about the dimensions of 904: * the plot. 905: */ 906: public void handleClick(int x, int y, PlotRenderingInfo info) { 907: // provides a 'no action' default 908: } 909: 910: /** 911: * Performs a zoom on the plot. Subclasses should override if zooming is 912: * appropriate for the type of plot. 913: * 914: * @param percent the zoom percentage. 915: */ 916: public void zoom(double percent) { 917: // do nothing by default. 918: } 919: 920: /** 921: * Receives notification of a change to one of the plot's axes. 922: * 923: * @param event information about the event (not used here). 924: */ 925: public void axisChanged(AxisChangeEvent event) { 926: notifyListeners(new PlotChangeEvent(this)); 927: } 928: 929: /** 930: * Receives notification of a change to the plot's dataset. 931: * <P> 932: * The plot reacts by passing on a plot change event to all registered 933: * listeners. 934: * 935: * @param event information about the event (not used here). 936: */ 937: public void datasetChanged(DatasetChangeEvent event) { 938: PlotChangeEvent newEvent = new PlotChangeEvent(this); 939: newEvent.setType(ChartChangeEventType.DATASET_UPDATED); 940: notifyListeners(newEvent); 941: } 942: 943: /** 944: * Adjusts the supplied x-value. 945: * 946: * @param x the x-value. 947: * @param w1 width 1. 948: * @param w2 width 2. 949: * @param edge the edge (left or right). 950: * 951: * @return The adjusted x-value. 952: */ 953: protected double getRectX(double x, double w1, double w2, 954: RectangleEdge edge) { 955: 956: double result = x; 957: if (edge == RectangleEdge.LEFT) { 958: result = result + w1; 959: } 960: else if (edge == RectangleEdge.RIGHT) { 961: result = result + w2; 962: } 963: return result; 964: 965: } 966: 967: /** 968: * Adjusts the supplied y-value. 969: * 970: * @param y the x-value. 971: * @param h1 height 1. 972: * @param h2 height 2. 973: * @param edge the edge (top or bottom). 974: * 975: * @return The adjusted y-value. 976: */ 977: protected double getRectY(double y, double h1, double h2, 978: RectangleEdge edge) { 979: 980: double result = y; 981: if (edge == RectangleEdge.TOP) { 982: result = result + h1; 983: } 984: else if (edge == RectangleEdge.BOTTOM) { 985: result = result + h2; 986: } 987: return result; 988: 989: } 990: 991: /** 992: * Tests this plot for equality with another object. 993: * 994: * @param obj the object (<code>null</code> permitted). 995: * 996: * @return <code>true</code> or <code>false</code>. 997: */ 998: public boolean equals(Object obj) { 999: if (obj == this) { 1000: return true; 1001: } 1002: if (!(obj instanceof Plot)) { 1003: return false; 1004: } 1005: Plot that = (Plot) obj; 1006: if (!ObjectUtilities.equal(this.noDataMessage, that.noDataMessage)) { 1007: return false; 1008: } 1009: if (!ObjectUtilities.equal( 1010: this.noDataMessageFont, that.noDataMessageFont 1011: )) { 1012: return false; 1013: } 1014: if (!PaintUtilities.equal( 1015: this.noDataMessagePaint, that.noDataMessagePaint 1016: )) { 1017: return false; 1018: } 1019: if (!ObjectUtilities.equal(this.insets, that.insets)) { 1020: return false; 1021: } 1022: if (!ObjectUtilities.equal(this.outlineStroke, that.outlineStroke)) { 1023: return false; 1024: } 1025: if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) { 1026: return false; 1027: } 1028: if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) { 1029: return false; 1030: } 1031: if (!ObjectUtilities.equal( 1032: this.backgroundImage, that.backgroundImage 1033: )) { 1034: return false; 1035: } 1036: if (this.backgroundImageAlignment != that.backgroundImageAlignment) { 1037: return false; 1038: } 1039: if (this.backgroundImageAlpha != that.backgroundImageAlpha) { 1040: return false; 1041: } 1042: if (this.foregroundAlpha != that.foregroundAlpha) { 1043: return false; 1044: } 1045: if (this.backgroundAlpha != that.backgroundAlpha) { 1046: return false; 1047: } 1048: if (!this.drawingSupplier.equals(that.drawingSupplier)) { 1049: return false; 1050: } 1051: return true; 1052: } 1053: 1054: /** 1055: * Creates a clone of the plot. 1056: * 1057: * @return A clone. 1058: * 1059: * @throws CloneNotSupportedException if some component of the plot does not 1060: * support cloning. 1061: */ 1062: public Object clone() throws CloneNotSupportedException { 1063: 1064: Plot clone = (Plot) super.clone(); 1065: // private Plot parent <-- don't clone the parent plot, but take care 1066: // childs in combined plots instead 1067: if (this.datasetGroup != null) { 1068: clone.datasetGroup 1069: = (DatasetGroup) ObjectUtilities.clone(this.datasetGroup); 1070: } 1071: clone.drawingSupplier 1072: = (DrawingSupplier) ObjectUtilities.clone(this.drawingSupplier); 1073: clone.listenerList = new EventListenerList(); 1074: return clone; 1075: 1076: } 1077: 1078: /** 1079: * Provides serialization support. 1080: * 1081: * @param stream the output stream. 1082: * 1083: * @throws IOException if there is an I/O error. 1084: */ 1085: private void writeObject(ObjectOutputStream stream) throws IOException { 1086: stream.defaultWriteObject(); 1087: SerialUtilities.writePaint(this.noDataMessagePaint, stream); 1088: SerialUtilities.writeStroke(this.outlineStroke, stream); 1089: SerialUtilities.writePaint(this.outlinePaint, stream); 1090: // backgroundImage 1091: SerialUtilities.writePaint(this.backgroundPaint, stream); 1092: } 1093: 1094: /** 1095: * Provides serialization support. 1096: * 1097: * @param stream the input stream. 1098: * 1099: * @throws IOException if there is an I/O error. 1100: * @throws ClassNotFoundException if there is a classpath problem. 1101: */ 1102: private void readObject(ObjectInputStream stream) 1103: throws IOException, ClassNotFoundException { 1104: stream.defaultReadObject(); 1105: this.noDataMessagePaint = SerialUtilities.readPaint(stream); 1106: this.outlineStroke = SerialUtilities.readStroke(stream); 1107: this.outlinePaint = SerialUtilities.readPaint(stream); 1108: // backgroundImage 1109: this.backgroundPaint = SerialUtilities.readPaint(stream); 1110: 1111: this.listenerList = new EventListenerList(); 1112: 1113: } 1114: 1115: /** 1116: * Resolves a domain axis location for a given plot orientation. 1117: * 1118: * @param location the location (<code>null</code> not permitted). 1119: * @param orientation the orientation (<code>null</code> not permitted). 1120: * 1121: * @return The edge (never <code>null</code>). 1122: */ 1123: public static RectangleEdge resolveDomainAxisLocation( 1124: AxisLocation location, PlotOrientation orientation) { 1125: 1126: if (location == null) { 1127: throw new IllegalArgumentException("Null 'location' argument."); 1128: } 1129: if (orientation == null) { 1130: throw new IllegalArgumentException("Null 'orientation' argument."); 1131: } 1132: 1133: RectangleEdge result = null; 1134: 1135: if (location == AxisLocation.TOP_OR_RIGHT) { 1136: if (orientation == PlotOrientation.HORIZONTAL) { 1137: result = RectangleEdge.RIGHT; 1138: } 1139: else if (orientation == PlotOrientation.VERTICAL) { 1140: result = RectangleEdge.TOP; 1141: } 1142: } 1143: else if (location == AxisLocation.TOP_OR_LEFT) { 1144: if (orientation == PlotOrientation.HORIZONTAL) { 1145: result = RectangleEdge.LEFT; 1146: } 1147: else if (orientation == PlotOrientation.VERTICAL) { 1148: result = RectangleEdge.TOP; 1149: } 1150: } 1151: else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1152: if (orientation == PlotOrientation.HORIZONTAL) { 1153: result = RectangleEdge.RIGHT; 1154: } 1155: else if (orientation == PlotOrientation.VERTICAL) { 1156: result = RectangleEdge.BOTTOM; 1157: } 1158: } 1159: else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1160: if (orientation == PlotOrientation.HORIZONTAL) { 1161: result = RectangleEdge.LEFT; 1162: } 1163: else if (orientation == PlotOrientation.VERTICAL) { 1164: result = RectangleEdge.BOTTOM; 1165: } 1166: } 1167: // the above should cover all the options... 1168: if (result == null) { 1169: throw new IllegalStateException("resolveDomainAxisLocation()"); 1170: } 1171: return result; 1172: 1173: } 1174: 1175: /** 1176: * Resolves a range axis location for a given plot orientation. 1177: * 1178: * @param location the location (<code>null</code> not permitted). 1179: * @param orientation the orientation (<code>null</code> not permitted). 1180: * 1181: * @return The edge (never <code>null</code>). 1182: */ 1183: public static RectangleEdge resolveRangeAxisLocation( 1184: AxisLocation location, PlotOrientation orientation) { 1185: 1186: if (location == null) { 1187: throw new IllegalArgumentException("Null 'location' argument."); 1188: } 1189: if (orientation == null) { 1190: throw new IllegalArgumentException("Null 'orientation' argument."); 1191: } 1192: 1193: RectangleEdge result = null; 1194: 1195: if (location == AxisLocation.TOP_OR_RIGHT) { 1196: if (orientation == PlotOrientation.HORIZONTAL) { 1197: result = RectangleEdge.TOP; 1198: } 1199: else if (orientation == PlotOrientation.VERTICAL) { 1200: result = RectangleEdge.RIGHT; 1201: } 1202: } 1203: else if (location == AxisLocation.TOP_OR_LEFT) { 1204: if (orientation == PlotOrientation.HORIZONTAL) { 1205: result = RectangleEdge.TOP; 1206: } 1207: else if (orientation == PlotOrientation.VERTICAL) { 1208: result = RectangleEdge.LEFT; 1209: } 1210: } 1211: else if (location == AxisLocation.BOTTOM_OR_RIGHT) { 1212: if (orientation == PlotOrientation.HORIZONTAL) { 1213: result = RectangleEdge.BOTTOM; 1214: } 1215: else if (orientation == PlotOrientation.VERTICAL) { 1216: result = RectangleEdge.RIGHT; 1217: } 1218: } 1219: else if (location == AxisLocation.BOTTOM_OR_LEFT) { 1220: if (orientation == PlotOrientation.HORIZONTAL) { 1221: result = RectangleEdge.BOTTOM; 1222: } 1223: else if (orientation == PlotOrientation.VERTICAL) { 1224: result = RectangleEdge.LEFT; 1225: } 1226: } 1227: 1228: // the above should cover all the options... 1229: if (result == null) { 1230: throw new IllegalStateException("resolveRangeAxisLocation()"); 1231: } 1232: return result; 1233: 1234: } 1235: 1236: }