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: * StandardXYItemRenderer.java 29: * --------------------------- 30: * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Mark Watson (www.markwatson.com); 34: * Jonathan Nash; 35: * Andreas Schneider; 36: * Norbert Kiesel (for TBD Networks); 37: * Christian W. Zuckschwerdt; 38: * Bill Kelemen; 39: * Nicolas Brodu (for Astrium and EADS Corporate Research 40: * Center); 41: * 42: * Changes: 43: * -------- 44: * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG); 45: * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG); 46: * 21-Dec-2001 : Added working line instance to improve performance (DG); 47: * 22-Jan-2002 : Added code to lock crosshairs to data points. Based on code 48: * by Jonathan Nash (DG); 49: * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 50: * 28-Mar-2002 : Added a property change listener mechanism so that the 51: * renderer no longer needs to be immutable (DG); 52: * 02-Apr-2002 : Modified to handle null values (DG); 53: * 09-Apr-2002 : Modified draw method to return void. Removed the translated 54: * zero from the drawItem method. Override the initialise() 55: * method to calculate it (DG); 56: * 13-May-2002 : Added code from Andreas Schneider to allow changing 57: * shapes/colors per item (DG); 58: * 24-May-2002 : Incorporated tooltips into chart entities (DG); 59: * 25-Jun-2002 : Removed redundant code (DG); 60: * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA); 61: * 08-Aug-2002 : Added discontinuous lines option contributed by 62: * Norbert Kiesel (DG); 63: * 20-Aug-2002 : Added user definable default values to be returned by 64: * protected methods unless overridden by a subclass (DG); 65: * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG); 66: * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG); 67: * 25-Mar-2003 : Implemented Serializable (DG); 68: * 01-May-2003 : Modified drawItem() method signature (DG); 69: * 15-May-2003 : Modified to take into account the plot orientation (DG); 70: * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG); 71: * 30-Jul-2003 : Modified entity constructor (CZ); 72: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 73: * 24-Aug-2003 : Added null/NaN checks in drawItem (BK); 74: * 08-Sep-2003 : Fixed serialization (NB); 75: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 76: * 21-Jan-2004 : Override for getLegendItem() method (DG); 77: * 27-Jan-2004 : Moved working line into state object (DG); 78: * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding 79: * easier (DG); 80: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed 81: * XYToolTipGenerator --> XYItemLabelGenerator (DG); 82: * 08-Jun-2004 : Modified to use getX() and getY() methods (DG); 83: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 84: * getYValue() (DG); 85: * 25-Aug-2004 : Created addEntity() method in superclass (DG); 86: * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG); 87: * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 88: * 23-Feb-2005 : Fixed getLegendItem() method to show lines. Fixed bug 89: * 1077108 (shape not visible for first item in series) (DG); 90: * 10-Apr-2005 : Fixed item label positioning with horizontal orientation (DG); 91: * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); 92: * 27-Apr-2005 : Use generator for series label in legend (DG); 93: * ------------- JFREECHART 1.0.x --------------------------------------------- 94: * 15-Jun-2006 : Fixed bug (1380480) for rendering series as path (DG); 95: * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG); 96: * 14-Mar-2007 : Fixed problems with the equals() and clone() methods (DG); 97: * 23-Mar-2007 : Clean-up of shapesFilled attributes (DG); 98: * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer 99: * change (DG); 100: * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() 101: * method (DG); 102: * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 103: * 08-Jun-2007 : Fixed bug in entity creation (DG); 104: * 21-Nov-2007 : Deprecated override flag methods (DG); 105: * 106: */ 107: 108: package org.jfree.chart.renderer.xy; 109: 110: import java.awt.Graphics2D; 111: import java.awt.Image; 112: import java.awt.Paint; 113: import java.awt.Point; 114: import java.awt.Shape; 115: import java.awt.Stroke; 116: import java.awt.geom.GeneralPath; 117: import java.awt.geom.Line2D; 118: import java.awt.geom.Rectangle2D; 119: import java.io.IOException; 120: import java.io.ObjectInputStream; 121: import java.io.ObjectOutputStream; 122: import java.io.Serializable; 123: 124: import org.jfree.chart.LegendItem; 125: import org.jfree.chart.axis.ValueAxis; 126: import org.jfree.chart.entity.EntityCollection; 127: import org.jfree.chart.event.RendererChangeEvent; 128: import org.jfree.chart.labels.XYToolTipGenerator; 129: import org.jfree.chart.plot.CrosshairState; 130: import org.jfree.chart.plot.Plot; 131: import org.jfree.chart.plot.PlotOrientation; 132: import org.jfree.chart.plot.PlotRenderingInfo; 133: import org.jfree.chart.plot.XYPlot; 134: import org.jfree.chart.urls.XYURLGenerator; 135: import org.jfree.data.xy.XYDataset; 136: import org.jfree.io.SerialUtilities; 137: import org.jfree.ui.RectangleEdge; 138: import org.jfree.util.BooleanList; 139: import org.jfree.util.BooleanUtilities; 140: import org.jfree.util.ObjectUtilities; 141: import org.jfree.util.PublicCloneable; 142: import org.jfree.util.ShapeUtilities; 143: import org.jfree.util.UnitType; 144: 145: /** 146: * Standard item renderer for an {@link XYPlot}. This class can draw (a) 147: * shapes at each point, or (b) lines between points, or (c) both shapes and 148: * lines. 149: * <P> 150: * This renderer has been retained for historical reasons and, in general, you 151: * should use the {@link XYLineAndShapeRenderer} class instead. 152: */ 153: public class StandardXYItemRenderer extends AbstractXYItemRenderer 154: implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 155: 156: /** For serialization. */ 157: private static final long serialVersionUID = -3271351259436865995L; 158: 159: /** Constant for the type of rendering (shapes only). */ 160: public static final int SHAPES = 1; 161: 162: /** Constant for the type of rendering (lines only). */ 163: public static final int LINES = 2; 164: 165: /** Constant for the type of rendering (shapes and lines). */ 166: public static final int SHAPES_AND_LINES = SHAPES | LINES; 167: 168: /** Constant for the type of rendering (images only). */ 169: public static final int IMAGES = 4; 170: 171: /** Constant for the type of rendering (discontinuous lines). */ 172: public static final int DISCONTINUOUS = 8; 173: 174: /** Constant for the type of rendering (discontinuous lines). */ 175: public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS; 176: 177: /** A flag indicating whether or not shapes are drawn at each XY point. */ 178: private boolean baseShapesVisible; 179: 180: /** A flag indicating whether or not lines are drawn between XY points. */ 181: private boolean plotLines; 182: 183: /** A flag indicating whether or not images are drawn between XY points. */ 184: private boolean plotImages; 185: 186: /** A flag controlling whether or not discontinuous lines are used. */ 187: private boolean plotDiscontinuous; 188: 189: /** Specifies how the gap threshold value is interpreted. */ 190: private UnitType gapThresholdType = UnitType.RELATIVE; 191: 192: /** Threshold for deciding when to discontinue a line. */ 193: private double gapThreshold = 1.0; 194: 195: /** 196: * A flag that controls whether or not shapes are filled for ALL series. 197: * 198: * @deprecated As of 1.0.8, this override should not be used. 199: */ 200: private Boolean shapesFilled; 201: 202: /** 203: * A table of flags that control (per series) whether or not shapes are 204: * filled. 205: */ 206: private BooleanList seriesShapesFilled; 207: 208: /** The default value returned by the getShapeFilled() method. */ 209: private boolean baseShapesFilled; 210: 211: /** 212: * A flag that controls whether or not each series is drawn as a single 213: * path. 214: */ 215: private boolean drawSeriesLineAsPath; 216: 217: /** 218: * The shape that is used to represent a line in the legend. 219: * This should never be set to <code>null</code>. 220: */ 221: private transient Shape legendLine; 222: 223: /** 224: * Constructs a new renderer. 225: */ 226: public StandardXYItemRenderer() { 227: this(LINES, null); 228: } 229: 230: /** 231: * Constructs a new renderer. To specify the type of renderer, use one of 232: * the constants: {@link #SHAPES}, {@link #LINES} or 233: * {@link #SHAPES_AND_LINES}. 234: * 235: * @param type the type. 236: */ 237: public StandardXYItemRenderer(int type) { 238: this(type, null); 239: } 240: 241: /** 242: * Constructs a new renderer. To specify the type of renderer, use one of 243: * the constants: {@link #SHAPES}, {@link #LINES} or 244: * {@link #SHAPES_AND_LINES}. 245: * 246: * @param type the type of renderer. 247: * @param toolTipGenerator the item label generator (<code>null</code> 248: * permitted). 249: */ 250: public StandardXYItemRenderer(int type, 251: XYToolTipGenerator toolTipGenerator) { 252: this(type, toolTipGenerator, null); 253: } 254: 255: /** 256: * Constructs a new renderer. To specify the type of renderer, use one of 257: * the constants: {@link #SHAPES}, {@link #LINES} or 258: * {@link #SHAPES_AND_LINES}. 259: * 260: * @param type the type of renderer. 261: * @param toolTipGenerator the item label generator (<code>null</code> 262: * permitted). 263: * @param urlGenerator the URL generator. 264: */ 265: public StandardXYItemRenderer(int type, 266: XYToolTipGenerator toolTipGenerator, 267: XYURLGenerator urlGenerator) { 268: 269: super(); 270: setBaseToolTipGenerator(toolTipGenerator); 271: setURLGenerator(urlGenerator); 272: if ((type & SHAPES) != 0) { 273: this.baseShapesVisible = true; 274: } 275: if ((type & LINES) != 0) { 276: this.plotLines = true; 277: } 278: if ((type & IMAGES) != 0) { 279: this.plotImages = true; 280: } 281: if ((type & DISCONTINUOUS) != 0) { 282: this.plotDiscontinuous = true; 283: } 284: 285: this.shapesFilled = null; 286: this.seriesShapesFilled = new BooleanList(); 287: this.baseShapesFilled = true; 288: this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 289: this.drawSeriesLineAsPath = false; 290: } 291: 292: /** 293: * Returns true if shapes are being plotted by the renderer. 294: * 295: * @return <code>true</code> if shapes are being plotted by the renderer. 296: * 297: * @see #setBaseShapesVisible 298: */ 299: public boolean getBaseShapesVisible() { 300: return this.baseShapesVisible; 301: } 302: 303: /** 304: * Sets the flag that controls whether or not a shape is plotted at each 305: * data point. 306: * 307: * @param flag the flag. 308: * 309: * @see #getBaseShapesVisible 310: */ 311: public void setBaseShapesVisible(boolean flag) { 312: if (this.baseShapesVisible != flag) { 313: this.baseShapesVisible = flag; 314: fireChangeEvent(); 315: } 316: } 317: 318: // SHAPES FILLED 319: 320: /** 321: * Returns the flag used to control whether or not the shape for an item is 322: * filled. 323: * <p> 324: * The default implementation passes control to the 325: * <code>getSeriesShapesFilled</code> method. You can override this method 326: * if you require different behaviour. 327: * 328: * @param series the series index (zero-based). 329: * @param item the item index (zero-based). 330: * 331: * @return A boolean. 332: * 333: * @see #getSeriesShapesFilled(int) 334: */ 335: public boolean getItemShapeFilled(int series, int item) { 336: // return the overall setting, if there is one... 337: if (this.shapesFilled != null) { 338: return this.shapesFilled.booleanValue(); 339: } 340: 341: // otherwise look up the paint table 342: Boolean flag = this.seriesShapesFilled.getBoolean(series); 343: if (flag != null) { 344: return flag.booleanValue(); 345: } 346: else { 347: return this.baseShapesFilled; 348: } 349: } 350: 351: /** 352: * Returns the override flag that controls whether or not shapes are filled 353: * for ALL series. 354: * 355: * @return The flag (possibly <code>null</code>). 356: * 357: * @since 1.0.5 358: * 359: * @deprecated As of 1.0.8, you should avoid using this method and rely 360: * on just the per-series ({@link #getSeriesShapesFilled(int)}) 361: * and base-level ({@link #getBaseShapesFilled()}) settings. 362: */ 363: public Boolean getShapesFilled() { 364: return this.shapesFilled; 365: } 366: 367: /** 368: * Sets the override flag that controls whether or not shapes are filled 369: * for ALL series and sends a {@link RendererChangeEvent} to all registered 370: * listeners. 371: * 372: * @param filled the flag. 373: * 374: * @see #setShapesFilled(Boolean) 375: * 376: * @deprecated As of 1.0.8, you should avoid using this method and rely 377: * on just the per-series ({@link #setSeriesShapesFilled(int, 378: * Boolean)}) and base-level ({@link #setBaseShapesVisible( 379: * boolean)}) settings. 380: */ 381: public void setShapesFilled(boolean filled) { 382: // here we use BooleanUtilities to remain compatible with JDKs < 1.4 383: setShapesFilled(BooleanUtilities.valueOf(filled)); 384: } 385: 386: /** 387: * Sets the override flag that controls whether or not shapes are filled 388: * for ALL series and sends a {@link RendererChangeEvent} to all registered 389: * listeners. 390: * 391: * @param filled the flag (<code>null</code> permitted). 392: * 393: * @see #setShapesFilled(boolean) 394: * 395: * @deprecated As of 1.0.8, you should avoid using this method and rely 396: * on just the per-series ({@link #setSeriesShapesFilled(int, 397: * Boolean)}) and base-level ({@link #setBaseShapesVisible( 398: * boolean)}) settings. 399: */ 400: public void setShapesFilled(Boolean filled) { 401: this.shapesFilled = filled; 402: fireChangeEvent(); 403: } 404: 405: /** 406: * Returns the flag used to control whether or not the shapes for a series 407: * are filled. 408: * 409: * @param series the series index (zero-based). 410: * 411: * @return A boolean. 412: */ 413: public Boolean getSeriesShapesFilled(int series) { 414: return this.seriesShapesFilled.getBoolean(series); 415: } 416: 417: /** 418: * Sets the 'shapes filled' flag for a series and sends a 419: * {@link RendererChangeEvent} to all registered listeners. 420: * 421: * @param series the series index (zero-based). 422: * @param flag the flag. 423: * 424: * @see #getSeriesShapesFilled(int) 425: */ 426: public void setSeriesShapesFilled(int series, Boolean flag) { 427: this.seriesShapesFilled.setBoolean(series, flag); 428: fireChangeEvent(); 429: } 430: 431: /** 432: * Returns the base 'shape filled' attribute. 433: * 434: * @return The base flag. 435: * 436: * @see #setBaseShapesFilled(boolean) 437: */ 438: public boolean getBaseShapesFilled() { 439: return this.baseShapesFilled; 440: } 441: 442: /** 443: * Sets the base 'shapes filled' flag and sends a 444: * {@link RendererChangeEvent} to all registered listeners. 445: * 446: * @param flag the flag. 447: * 448: * @see #getBaseShapesFilled() 449: */ 450: public void setBaseShapesFilled(boolean flag) { 451: this.baseShapesFilled = flag; 452: } 453: 454: /** 455: * Returns true if lines are being plotted by the renderer. 456: * 457: * @return <code>true</code> if lines are being plotted by the renderer. 458: * 459: * @see #setPlotLines(boolean) 460: */ 461: public boolean getPlotLines() { 462: return this.plotLines; 463: } 464: 465: /** 466: * Sets the flag that controls whether or not a line is plotted between 467: * each data point and sends a {@link RendererChangeEvent} to all 468: * registered listeners. 469: * 470: * @param flag the flag. 471: * 472: * @see #getPlotLines() 473: */ 474: public void setPlotLines(boolean flag) { 475: if (this.plotLines != flag) { 476: this.plotLines = flag; 477: fireChangeEvent(); 478: } 479: } 480: 481: /** 482: * Returns the gap threshold type (relative or absolute). 483: * 484: * @return The type. 485: * 486: * @see #setGapThresholdType(UnitType) 487: */ 488: public UnitType getGapThresholdType() { 489: return this.gapThresholdType; 490: } 491: 492: /** 493: * Sets the gap threshold type and sends a {@link RendererChangeEvent} to 494: * all registered listeners. 495: * 496: * @param thresholdType the type (<code>null</code> not permitted). 497: * 498: * @see #getGapThresholdType() 499: */ 500: public void setGapThresholdType(UnitType thresholdType) { 501: if (thresholdType == null) { 502: throw new IllegalArgumentException( 503: "Null 'thresholdType' argument."); 504: } 505: this.gapThresholdType = thresholdType; 506: fireChangeEvent(); 507: } 508: 509: /** 510: * Returns the gap threshold for discontinuous lines. 511: * 512: * @return The gap threshold. 513: * 514: * @see #setGapThreshold(double) 515: */ 516: public double getGapThreshold() { 517: return this.gapThreshold; 518: } 519: 520: /** 521: * Sets the gap threshold for discontinuous lines and sends a 522: * {@link RendererChangeEvent} to all registered listeners. 523: * 524: * @param t the threshold. 525: * 526: * @see #getGapThreshold() 527: */ 528: public void setGapThreshold(double t) { 529: this.gapThreshold = t; 530: fireChangeEvent(); 531: } 532: 533: /** 534: * Returns true if images are being plotted by the renderer. 535: * 536: * @return <code>true</code> if images are being plotted by the renderer. 537: * 538: * @see #setPlotImages(boolean) 539: */ 540: public boolean getPlotImages() { 541: return this.plotImages; 542: } 543: 544: /** 545: * Sets the flag that controls whether or not an image is drawn at each 546: * data point and sends a {@link RendererChangeEvent} to all registered 547: * listeners. 548: * 549: * @param flag the flag. 550: * 551: * @see #getPlotImages() 552: */ 553: public void setPlotImages(boolean flag) { 554: if (this.plotImages != flag) { 555: this.plotImages = flag; 556: fireChangeEvent(); 557: } 558: } 559: 560: /** 561: * Returns a flag that controls whether or not the renderer shows 562: * discontinuous lines. 563: * 564: * @return <code>true</code> if lines should be discontinuous. 565: */ 566: public boolean getPlotDiscontinuous() { 567: return this.plotDiscontinuous; 568: } 569: 570: /** 571: * Sets the flag that controls whether or not the renderer shows 572: * discontinuous lines, and sends a {@link RendererChangeEvent} to all 573: * registered listeners. 574: * 575: * @param flag the new flag value. 576: * 577: * @since 1.0.5 578: */ 579: public void setPlotDiscontinuous(boolean flag) { 580: if (this.plotDiscontinuous != flag) { 581: this.plotDiscontinuous = flag; 582: fireChangeEvent(); 583: } 584: } 585: 586: /** 587: * Returns a flag that controls whether or not each series is drawn as a 588: * single path. 589: * 590: * @return A boolean. 591: * 592: * @see #setDrawSeriesLineAsPath(boolean) 593: */ 594: public boolean getDrawSeriesLineAsPath() { 595: return this.drawSeriesLineAsPath; 596: } 597: 598: /** 599: * Sets the flag that controls whether or not each series is drawn as a 600: * single path. 601: * 602: * @param flag the flag. 603: * 604: * @see #getDrawSeriesLineAsPath() 605: */ 606: public void setDrawSeriesLineAsPath(boolean flag) { 607: this.drawSeriesLineAsPath = flag; 608: } 609: 610: /** 611: * Returns the shape used to represent a line in the legend. 612: * 613: * @return The legend line (never <code>null</code>). 614: * 615: * @see #setLegendLine(Shape) 616: */ 617: public Shape getLegendLine() { 618: return this.legendLine; 619: } 620: 621: /** 622: * Sets the shape used as a line in each legend item and sends a 623: * {@link RendererChangeEvent} to all registered listeners. 624: * 625: * @param line the line (<code>null</code> not permitted). 626: * 627: * @see #getLegendLine() 628: */ 629: public void setLegendLine(Shape line) { 630: if (line == null) { 631: throw new IllegalArgumentException("Null 'line' argument."); 632: } 633: this.legendLine = line; 634: fireChangeEvent(); 635: } 636: 637: /** 638: * Returns a legend item for a series. 639: * 640: * @param datasetIndex the dataset index (zero-based). 641: * @param series the series index (zero-based). 642: * 643: * @return A legend item for the series. 644: */ 645: public LegendItem getLegendItem(int datasetIndex, int series) { 646: XYPlot plot = getPlot(); 647: if (plot == null) { 648: return null; 649: } 650: LegendItem result = null; 651: XYDataset dataset = plot.getDataset(datasetIndex); 652: if (dataset != null) { 653: if (getItemVisible(series, 0)) { 654: String label = getLegendItemLabelGenerator().generateLabel( 655: dataset, series); 656: String description = label; 657: String toolTipText = null; 658: if (getLegendItemToolTipGenerator() != null) { 659: toolTipText = getLegendItemToolTipGenerator().generateLabel( 660: dataset, series); 661: } 662: String urlText = null; 663: if (getLegendItemURLGenerator() != null) { 664: urlText = getLegendItemURLGenerator().generateLabel( 665: dataset, series); 666: } 667: Shape shape = lookupSeriesShape(series); 668: boolean shapeFilled = getItemShapeFilled(series, 0); 669: Paint paint = lookupSeriesPaint(series); 670: Paint linePaint = paint; 671: Stroke lineStroke = lookupSeriesStroke(series); 672: result = new LegendItem(label, description, toolTipText, 673: urlText, this.baseShapesVisible, shape, shapeFilled, 674: paint, !shapeFilled, paint, lineStroke, 675: this.plotLines, this.legendLine, lineStroke, linePaint); 676: result.setDataset(dataset); 677: result.setDatasetIndex(datasetIndex); 678: result.setSeriesKey(dataset.getSeriesKey(series)); 679: result.setSeriesIndex(series); 680: } 681: } 682: return result; 683: } 684: 685: /** 686: * Records the state for the renderer. This is used to preserve state 687: * information between calls to the drawItem() method for a single chart 688: * drawing. 689: */ 690: public static class State extends XYItemRendererState { 691: 692: /** The path for the current series. */ 693: public GeneralPath seriesPath; 694: 695: /** The series index. */ 696: private int seriesIndex; 697: 698: /** 699: * A flag that indicates if the last (x, y) point was 'good' 700: * (non-null). 701: */ 702: private boolean lastPointGood; 703: 704: /** 705: * Creates a new state instance. 706: * 707: * @param info the plot rendering info. 708: */ 709: public State(PlotRenderingInfo info) { 710: super(info); 711: } 712: 713: /** 714: * Returns a flag that indicates if the last point drawn (in the 715: * current series) was 'good' (non-null). 716: * 717: * @return A boolean. 718: */ 719: public boolean isLastPointGood() { 720: return this.lastPointGood; 721: } 722: 723: /** 724: * Sets a flag that indicates if the last point drawn (in the current 725: * series) was 'good' (non-null). 726: * 727: * @param good the flag. 728: */ 729: public void setLastPointGood(boolean good) { 730: this.lastPointGood = good; 731: } 732: 733: /** 734: * Returns the series index for the current path. 735: * 736: * @return The series index for the current path. 737: */ 738: public int getSeriesIndex() { 739: return this.seriesIndex; 740: } 741: 742: /** 743: * Sets the series index for the current path. 744: * 745: * @param index the index. 746: */ 747: public void setSeriesIndex(int index) { 748: this.seriesIndex = index; 749: } 750: } 751: 752: /** 753: * Initialises the renderer. 754: * <P> 755: * This method will be called before the first item is rendered, giving the 756: * renderer an opportunity to initialise any state information it wants to 757: * maintain. The renderer can do nothing if it chooses. 758: * 759: * @param g2 the graphics device. 760: * @param dataArea the area inside the axes. 761: * @param plot the plot. 762: * @param data the data. 763: * @param info an optional info collection object to return data back to 764: * the caller. 765: * 766: * @return The renderer state. 767: */ 768: public XYItemRendererState initialise(Graphics2D g2, 769: Rectangle2D dataArea, 770: XYPlot plot, 771: XYDataset data, 772: PlotRenderingInfo info) { 773: 774: State state = new State(info); 775: state.seriesPath = new GeneralPath(); 776: state.seriesIndex = -1; 777: return state; 778: 779: } 780: 781: /** 782: * Draws the visual representation of a single data item. 783: * 784: * @param g2 the graphics device. 785: * @param state the renderer state. 786: * @param dataArea the area within which the data is being drawn. 787: * @param info collects information about the drawing. 788: * @param plot the plot (can be used to obtain standard color information 789: * etc). 790: * @param domainAxis the domain axis. 791: * @param rangeAxis the range axis. 792: * @param dataset the dataset. 793: * @param series the series index (zero-based). 794: * @param item the item index (zero-based). 795: * @param crosshairState crosshair information for the plot 796: * (<code>null</code> permitted). 797: * @param pass the pass index. 798: */ 799: public void drawItem(Graphics2D g2, 800: XYItemRendererState state, 801: Rectangle2D dataArea, 802: PlotRenderingInfo info, 803: XYPlot plot, 804: ValueAxis domainAxis, 805: ValueAxis rangeAxis, 806: XYDataset dataset, 807: int series, 808: int item, 809: CrosshairState crosshairState, 810: int pass) { 811: 812: boolean itemVisible = getItemVisible(series, item); 813: 814: // setup for collecting optional entity info... 815: Shape entityArea = null; 816: EntityCollection entities = null; 817: if (info != null) { 818: entities = info.getOwner().getEntityCollection(); 819: } 820: 821: PlotOrientation orientation = plot.getOrientation(); 822: Paint paint = getItemPaint(series, item); 823: Stroke seriesStroke = getItemStroke(series, item); 824: g2.setPaint(paint); 825: g2.setStroke(seriesStroke); 826: 827: // get the data point... 828: double x1 = dataset.getXValue(series, item); 829: double y1 = dataset.getYValue(series, item); 830: if (Double.isNaN(x1) || Double.isNaN(y1)) { 831: itemVisible = false; 832: } 833: 834: RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 835: RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 836: double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 837: double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 838: 839: if (getPlotLines()) { 840: if (this.drawSeriesLineAsPath) { 841: State s = (State) state; 842: if (s.getSeriesIndex() != series) { 843: // we are starting a new series path 844: s.seriesPath.reset(); 845: s.lastPointGood = false; 846: s.setSeriesIndex(series); 847: } 848: 849: // update path to reflect latest point 850: if (itemVisible && !Double.isNaN(transX1) 851: && !Double.isNaN(transY1)) { 852: float x = (float) transX1; 853: float y = (float) transY1; 854: if (orientation == PlotOrientation.HORIZONTAL) { 855: x = (float) transY1; 856: y = (float) transX1; 857: } 858: if (s.isLastPointGood()) { 859: // TODO: check threshold 860: s.seriesPath.lineTo(x, y); 861: } 862: else { 863: s.seriesPath.moveTo(x, y); 864: } 865: s.setLastPointGood(true); 866: } 867: else { 868: s.setLastPointGood(false); 869: } 870: if (item == dataset.getItemCount(series) - 1) { 871: if (s.seriesIndex == series) { 872: // draw path 873: g2.setStroke(lookupSeriesStroke(series)); 874: g2.setPaint(lookupSeriesPaint(series)); 875: g2.draw(s.seriesPath); 876: } 877: } 878: } 879: 880: else if (item != 0 && itemVisible) { 881: // get the previous data point... 882: double x0 = dataset.getXValue(series, item - 1); 883: double y0 = dataset.getYValue(series, item - 1); 884: if (!Double.isNaN(x0) && !Double.isNaN(y0)) { 885: boolean drawLine = true; 886: if (getPlotDiscontinuous()) { 887: // only draw a line if the gap between the current and 888: // previous data point is within the threshold 889: int numX = dataset.getItemCount(series); 890: double minX = dataset.getXValue(series, 0); 891: double maxX = dataset.getXValue(series, numX - 1); 892: if (this.gapThresholdType == UnitType.ABSOLUTE) { 893: drawLine = Math.abs(x1 - x0) <= this.gapThreshold; 894: } 895: else { 896: drawLine = Math.abs(x1 - x0) <= ((maxX - minX) 897: / numX * getGapThreshold()); 898: } 899: } 900: if (drawLine) { 901: double transX0 = domainAxis.valueToJava2D(x0, dataArea, 902: xAxisLocation); 903: double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 904: yAxisLocation); 905: 906: // only draw if we have good values 907: if (Double.isNaN(transX0) || Double.isNaN(transY0) 908: || Double.isNaN(transX1) || Double.isNaN(transY1)) { 909: return; 910: } 911: 912: if (orientation == PlotOrientation.HORIZONTAL) { 913: state.workingLine.setLine(transY0, transX0, 914: transY1, transX1); 915: } 916: else if (orientation == PlotOrientation.VERTICAL) { 917: state.workingLine.setLine(transX0, transY0, 918: transX1, transY1); 919: } 920: 921: if (state.workingLine.intersects(dataArea)) { 922: g2.draw(state.workingLine); 923: } 924: } 925: } 926: } 927: } 928: 929: // we needed to get this far even for invisible items, to ensure that 930: // seriesPath updates happened, but now there is nothing more we need 931: // to do for non-visible items... 932: if (!itemVisible) { 933: return; 934: } 935: 936: if (getBaseShapesVisible()) { 937: 938: Shape shape = getItemShape(series, item); 939: if (orientation == PlotOrientation.HORIZONTAL) { 940: shape = ShapeUtilities.createTranslatedShape(shape, transY1, 941: transX1); 942: } 943: else if (orientation == PlotOrientation.VERTICAL) { 944: shape = ShapeUtilities.createTranslatedShape(shape, transX1, 945: transY1); 946: } 947: if (shape.intersects(dataArea)) { 948: if (getItemShapeFilled(series, item)) { 949: g2.fill(shape); 950: } 951: else { 952: g2.draw(shape); 953: } 954: } 955: entityArea = shape; 956: 957: } 958: 959: if (getPlotImages()) { 960: Image image = getImage(plot, series, item, transX1, transY1); 961: if (image != null) { 962: Point hotspot = getImageHotspot(plot, series, item, transX1, 963: transY1, image); 964: g2.drawImage(image, (int) (transX1 - hotspot.getX()), 965: (int) (transY1 - hotspot.getY()), null); 966: entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(), 967: transY1 - hotspot.getY(), image.getWidth(null), 968: image.getHeight(null)); 969: } 970: 971: } 972: 973: double xx = transX1; 974: double yy = transY1; 975: if (orientation == PlotOrientation.HORIZONTAL) { 976: xx = transY1; 977: yy = transX1; 978: } 979: 980: // draw the item label if there is one... 981: if (isItemLabelVisible(series, item)) { 982: drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 983: (y1 < 0.0)); 984: } 985: 986: int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 987: int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 988: updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 989: rangeAxisIndex, transX1, transY1, orientation); 990: 991: // add an entity for the item... 992: if (entities != null && dataArea.contains(xx, yy)) { 993: addEntity(entities, entityArea, dataset, series, item, xx, yy); 994: } 995: 996: } 997: 998: /** 999: * Tests this renderer for equality with another object. 1000: * 1001: * @param obj the object (<code>null</code> permitted). 1002: * 1003: * @return A boolean. 1004: */ 1005: public boolean equals(Object obj) { 1006: 1007: if (obj == this) { 1008: return true; 1009: } 1010: if (!(obj instanceof StandardXYItemRenderer)) { 1011: return false; 1012: } 1013: StandardXYItemRenderer that = (StandardXYItemRenderer) obj; 1014: if (this.baseShapesVisible != that.baseShapesVisible) { 1015: return false; 1016: } 1017: if (this.plotLines != that.plotLines) { 1018: return false; 1019: } 1020: if (this.plotImages != that.plotImages) { 1021: return false; 1022: } 1023: if (this.plotDiscontinuous != that.plotDiscontinuous) { 1024: return false; 1025: } 1026: if (this.gapThresholdType != that.gapThresholdType) { 1027: return false; 1028: } 1029: if (this.gapThreshold != that.gapThreshold) { 1030: return false; 1031: } 1032: if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1033: return false; 1034: } 1035: if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) { 1036: return false; 1037: } 1038: if (this.baseShapesFilled != that.baseShapesFilled) { 1039: return false; 1040: } 1041: if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) { 1042: return false; 1043: } 1044: if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 1045: return false; 1046: } 1047: return super.equals(obj); 1048: 1049: } 1050: 1051: /** 1052: * Returns a clone of the renderer. 1053: * 1054: * @return A clone. 1055: * 1056: * @throws CloneNotSupportedException if the renderer cannot be cloned. 1057: */ 1058: public Object clone() throws CloneNotSupportedException { 1059: StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone(); 1060: clone.seriesShapesFilled 1061: = (BooleanList) this.seriesShapesFilled.clone(); 1062: clone.legendLine = ShapeUtilities.clone(this.legendLine); 1063: return clone; 1064: } 1065: 1066: //////////////////////////////////////////////////////////////////////////// 1067: // PROTECTED METHODS 1068: // These provide the opportunity to subclass the standard renderer and 1069: // create custom effects. 1070: //////////////////////////////////////////////////////////////////////////// 1071: 1072: /** 1073: * Returns the image used to draw a single data item. 1074: * 1075: * @param plot the plot (can be used to obtain standard color information 1076: * etc). 1077: * @param series the series index. 1078: * @param item the item index. 1079: * @param x the x value of the item. 1080: * @param y the y value of the item. 1081: * 1082: * @return The image. 1083: * 1084: * @see #getPlotImages() 1085: */ 1086: protected Image getImage(Plot plot, int series, int item, 1087: double x, double y) { 1088: // this method must be overridden if you want to display images 1089: return null; 1090: } 1091: 1092: /** 1093: * Returns the hotspot of the image used to draw a single data item. 1094: * The hotspot is the point relative to the top left of the image 1095: * that should indicate the data item. The default is the center of the 1096: * image. 1097: * 1098: * @param plot the plot (can be used to obtain standard color information 1099: * etc). 1100: * @param image the image (can be used to get size information about the 1101: * image) 1102: * @param series the series index 1103: * @param item the item index 1104: * @param x the x value of the item 1105: * @param y the y value of the item 1106: * 1107: * @return The hotspot used to draw the data item. 1108: */ 1109: protected Point getImageHotspot(Plot plot, int series, int item, 1110: double x, double y, Image image) { 1111: 1112: int height = image.getHeight(null); 1113: int width = image.getWidth(null); 1114: return new Point(width / 2, height / 2); 1115: 1116: } 1117: 1118: /** 1119: * Provides serialization support. 1120: * 1121: * @param stream the input stream. 1122: * 1123: * @throws IOException if there is an I/O error. 1124: * @throws ClassNotFoundException if there is a classpath problem. 1125: */ 1126: private void readObject(ObjectInputStream stream) 1127: throws IOException, ClassNotFoundException { 1128: stream.defaultReadObject(); 1129: this.legendLine = SerialUtilities.readShape(stream); 1130: } 1131: 1132: /** 1133: * Provides serialization support. 1134: * 1135: * @param stream the output stream. 1136: * 1137: * @throws IOException if there is an I/O error. 1138: */ 1139: private void writeObject(ObjectOutputStream stream) throws IOException { 1140: stream.defaultWriteObject(); 1141: SerialUtilities.writeShape(this.legendLine, stream); 1142: } 1143: 1144: }