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