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