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