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