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