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: * XYLineAndShapeRenderer.java 29: * --------------------------- 30: * (C) Copyright 2004-2007, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): -; 34: * 35: * Changes: 36: * -------- 37: * 27-Jan-2004 : Version 1 (DG); 38: * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste 39: * overriding easier (DG); 40: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 41: * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG); 42: * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path 43: * (necessary when using a dashed stroke with many data 44: * items) (DG); 45: * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG); 46: * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 47: * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG); 48: * 28-Jan-2005 : Added new constructor (DG); 49: * 09-Mar-2005 : Added fillPaint settings (DG); 50: * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); 51: * 22-Jul-2005 : Renamed defaultLinesVisible --> baseLinesVisible, 52: * defaultShapesVisible --> baseShapesVisible and 53: * defaultShapesFilled --> baseShapesFilled (DG); 54: * 29-Jul-2005 : Added code to draw item labels (DG); 55: * ------------- JFREECHART 1.0.x --------------------------------------------- 56: * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG); 57: * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG); 58: * 21-Feb-2007 : Fixed bugs in clone() and equals() (DG); 59: * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 60: * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 61: * 08-Jun-2007 : Fix for bug 1731912 where entities are created even for data 62: * items that are not displayed (DG); 63: * 26-Oct-2007 : Deprecated override attributes (DG); 64: * 65: */ 66: 67: package org.jfree.chart.renderer.xy; 68: 69: import java.awt.Graphics2D; 70: import java.awt.Paint; 71: import java.awt.Shape; 72: import java.awt.Stroke; 73: import java.awt.geom.GeneralPath; 74: import java.awt.geom.Line2D; 75: import java.awt.geom.Rectangle2D; 76: import java.io.IOException; 77: import java.io.ObjectInputStream; 78: import java.io.ObjectOutputStream; 79: import java.io.Serializable; 80: 81: import org.jfree.chart.LegendItem; 82: import org.jfree.chart.axis.ValueAxis; 83: import org.jfree.chart.entity.EntityCollection; 84: import org.jfree.chart.event.RendererChangeEvent; 85: import org.jfree.chart.plot.CrosshairState; 86: import org.jfree.chart.plot.PlotOrientation; 87: import org.jfree.chart.plot.PlotRenderingInfo; 88: import org.jfree.chart.plot.XYPlot; 89: import org.jfree.data.xy.XYDataset; 90: import org.jfree.io.SerialUtilities; 91: import org.jfree.ui.RectangleEdge; 92: import org.jfree.util.BooleanList; 93: import org.jfree.util.BooleanUtilities; 94: import org.jfree.util.ObjectUtilities; 95: import org.jfree.util.PublicCloneable; 96: import org.jfree.util.ShapeUtilities; 97: 98: /** 99: * A renderer that connects data points with lines and/or draws shapes at each 100: * data point. This renderer is designed for use with the {@link XYPlot} 101: * class. 102: */ 103: public class XYLineAndShapeRenderer extends AbstractXYItemRenderer 104: implements XYItemRenderer, 105: Cloneable, 106: PublicCloneable, 107: Serializable { 108: 109: /** For serialization. */ 110: private static final long serialVersionUID = -7435246895986425885L; 111: 112: /** 113: * A flag that controls whether or not lines are visible for ALL series. 114: * 115: * @deprecated As of 1.0.7. 116: */ 117: private Boolean linesVisible; 118: 119: /** 120: * A table of flags that control (per series) whether or not lines are 121: * visible. 122: */ 123: private BooleanList seriesLinesVisible; 124: 125: /** The default value returned by the getLinesVisible() method. */ 126: private boolean baseLinesVisible; 127: 128: /** The shape that is used to represent a line in the legend. */ 129: private transient Shape legendLine; 130: 131: /** 132: * A flag that controls whether or not shapes are visible for ALL series. 133: * 134: * @deprecated As of 1.0.7. 135: */ 136: private Boolean shapesVisible; 137: 138: /** 139: * A table of flags that control (per series) whether or not shapes are 140: * visible. 141: */ 142: private BooleanList seriesShapesVisible; 143: 144: /** The default value returned by the getShapeVisible() method. */ 145: private boolean baseShapesVisible; 146: 147: /** 148: * A flag that controls whether or not shapes are filled for ALL series. 149: * 150: * @deprecated As of 1.0.7. 151: */ 152: private Boolean shapesFilled; 153: 154: /** 155: * A table of flags that control (per series) whether or not shapes are 156: * filled. 157: */ 158: private BooleanList seriesShapesFilled; 159: 160: /** The default value returned by the getShapeFilled() method. */ 161: private boolean baseShapesFilled; 162: 163: /** A flag that controls whether outlines are drawn for shapes. */ 164: private boolean drawOutlines; 165: 166: /** 167: * A flag that controls whether the fill paint is used for filling 168: * shapes. 169: */ 170: private boolean useFillPaint; 171: 172: /** 173: * A flag that controls whether the outline paint is used for drawing shape 174: * outlines. 175: */ 176: private boolean useOutlinePaint; 177: 178: /** 179: * A flag that controls whether or not each series is drawn as a single 180: * path. 181: */ 182: private boolean drawSeriesLineAsPath; 183: 184: /** 185: * Creates a new renderer with both lines and shapes visible. 186: */ 187: public XYLineAndShapeRenderer() { 188: this(true, true); 189: } 190: 191: /** 192: * Creates a new renderer. 193: * 194: * @param lines lines visible? 195: * @param shapes shapes visible? 196: */ 197: public XYLineAndShapeRenderer(boolean lines, boolean shapes) { 198: this.linesVisible = null; 199: this.seriesLinesVisible = new BooleanList(); 200: this.baseLinesVisible = lines; 201: this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 202: 203: this.shapesVisible = null; 204: this.seriesShapesVisible = new BooleanList(); 205: this.baseShapesVisible = shapes; 206: 207: this.shapesFilled = null; 208: this.useFillPaint = false; // use item paint for fills by default 209: this.seriesShapesFilled = new BooleanList(); 210: this.baseShapesFilled = true; 211: 212: this.drawOutlines = true; 213: this.useOutlinePaint = false; // use item paint for outlines by 214: // default, not outline paint 215: 216: this.drawSeriesLineAsPath = false; 217: } 218: 219: /** 220: * Returns a flag that controls whether or not each series is drawn as a 221: * single path. 222: * 223: * @return A boolean. 224: * 225: * @see #setDrawSeriesLineAsPath(boolean) 226: */ 227: public boolean getDrawSeriesLineAsPath() { 228: return this.drawSeriesLineAsPath; 229: } 230: 231: /** 232: * Sets the flag that controls whether or not each series is drawn as a 233: * single path and sends a {@link RendererChangeEvent} to all registered 234: * listeners. 235: * 236: * @param flag the flag. 237: * 238: * @see #getDrawSeriesLineAsPath() 239: */ 240: public void setDrawSeriesLineAsPath(boolean flag) { 241: if (this.drawSeriesLineAsPath != flag) { 242: this.drawSeriesLineAsPath = flag; 243: fireChangeEvent(); 244: } 245: } 246: 247: /** 248: * Returns the number of passes through the data that the renderer requires 249: * in order to draw the chart. Most charts will require a single pass, but 250: * some require two passes. 251: * 252: * @return The pass count. 253: */ 254: public int getPassCount() { 255: return 2; 256: } 257: 258: // LINES VISIBLE 259: 260: /** 261: * Returns the flag used to control whether or not the shape for an item is 262: * visible. 263: * 264: * @param series the series index (zero-based). 265: * @param item the item index (zero-based). 266: * 267: * @return A boolean. 268: */ 269: public boolean getItemLineVisible(int series, int item) { 270: Boolean flag = this.linesVisible; 271: if (flag == null) { 272: flag = getSeriesLinesVisible(series); 273: } 274: if (flag != null) { 275: return flag.booleanValue(); 276: } 277: else { 278: return this.baseLinesVisible; 279: } 280: } 281: 282: /** 283: * Returns a flag that controls whether or not lines are drawn for ALL 284: * series. If this flag is <code>null</code>, then the "per series" 285: * settings will apply. 286: * 287: * @return A flag (possibly <code>null</code>). 288: * 289: * @see #setLinesVisible(Boolean) 290: * 291: * @deprecated As of 1.0.7, use the per-series and base level settings. 292: */ 293: public Boolean getLinesVisible() { 294: return this.linesVisible; 295: } 296: 297: /** 298: * Sets a flag that controls whether or not lines are drawn between the 299: * items in ALL series, and sends a {@link RendererChangeEvent} to all 300: * registered listeners. You need to set this to <code>null</code> if you 301: * want the "per series" settings to apply. 302: * 303: * @param visible the flag (<code>null</code> permitted). 304: * 305: * @see #getLinesVisible() 306: * 307: * @deprecated As of 1.0.7, use the per-series and base level settings. 308: */ 309: public void setLinesVisible(Boolean visible) { 310: this.linesVisible = visible; 311: fireChangeEvent(); 312: } 313: 314: /** 315: * Sets a flag that controls whether or not lines are drawn between the 316: * items in ALL series, and sends a {@link RendererChangeEvent} to all 317: * registered listeners. 318: * 319: * @param visible the flag. 320: * 321: * @see #getLinesVisible() 322: * 323: * @deprecated As of 1.0.7, use the per-series and base level settings. 324: */ 325: public void setLinesVisible(boolean visible) { 326: // we use BooleanUtilities here to preserve JRE 1.3.1 compatibility 327: setLinesVisible(BooleanUtilities.valueOf(visible)); 328: } 329: 330: /** 331: * Returns the flag used to control whether or not the lines for a series 332: * are visible. 333: * 334: * @param series the series index (zero-based). 335: * 336: * @return The flag (possibly <code>null</code>). 337: * 338: * @see #setSeriesLinesVisible(int, Boolean) 339: */ 340: public Boolean getSeriesLinesVisible(int series) { 341: return this.seriesLinesVisible.getBoolean(series); 342: } 343: 344: /** 345: * Sets the 'lines visible' flag for a series and sends a 346: * {@link RendererChangeEvent} to all registered listeners. 347: * 348: * @param series the series index (zero-based). 349: * @param flag the flag (<code>null</code> permitted). 350: * 351: * @see #getSeriesLinesVisible(int) 352: */ 353: public void setSeriesLinesVisible(int series, Boolean flag) { 354: this.seriesLinesVisible.setBoolean(series, flag); 355: fireChangeEvent(); 356: } 357: 358: /** 359: * Sets the 'lines visible' flag for a series and sends a 360: * {@link RendererChangeEvent} to all registered listeners. 361: * 362: * @param series the series index (zero-based). 363: * @param visible the flag. 364: * 365: * @see #getSeriesLinesVisible(int) 366: */ 367: public void setSeriesLinesVisible(int series, boolean visible) { 368: setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible)); 369: } 370: 371: /** 372: * Returns the base 'lines visible' attribute. 373: * 374: * @return The base flag. 375: * 376: * @see #setBaseLinesVisible(boolean) 377: */ 378: public boolean getBaseLinesVisible() { 379: return this.baseLinesVisible; 380: } 381: 382: /** 383: * Sets the base 'lines visible' flag and sends a 384: * {@link RendererChangeEvent} to all registered listeners. 385: * 386: * @param flag the flag. 387: * 388: * @see #getBaseLinesVisible() 389: */ 390: public void setBaseLinesVisible(boolean flag) { 391: this.baseLinesVisible = flag; 392: fireChangeEvent(); 393: } 394: 395: /** 396: * Returns the shape used to represent a line in the legend. 397: * 398: * @return The legend line (never <code>null</code>). 399: * 400: * @see #setLegendLine(Shape) 401: */ 402: public Shape getLegendLine() { 403: return this.legendLine; 404: } 405: 406: /** 407: * Sets the shape used as a line in each legend item and sends a 408: * {@link RendererChangeEvent} to all registered listeners. 409: * 410: * @param line the line (<code>null</code> not permitted). 411: * 412: * @see #getLegendLine() 413: */ 414: public void setLegendLine(Shape line) { 415: if (line == null) { 416: throw new IllegalArgumentException("Null 'line' argument."); 417: } 418: this.legendLine = line; 419: fireChangeEvent(); 420: } 421: 422: // SHAPES VISIBLE 423: 424: /** 425: * Returns the flag used to control whether or not the shape for an item is 426: * visible. 427: * <p> 428: * The default implementation passes control to the 429: * <code>getSeriesShapesVisible</code> method. You can override this method 430: * if you require different behaviour. 431: * 432: * @param series the series index (zero-based). 433: * @param item the item index (zero-based). 434: * 435: * @return A boolean. 436: */ 437: public boolean getItemShapeVisible(int series, int item) { 438: Boolean flag = this.shapesVisible; 439: if (flag == null) { 440: flag = getSeriesShapesVisible(series); 441: } 442: if (flag != null) { 443: return flag.booleanValue(); 444: } 445: else { 446: return this.baseShapesVisible; 447: } 448: } 449: 450: /** 451: * Returns the flag that controls whether the shapes are visible for the 452: * items in ALL series. 453: * 454: * @return The flag (possibly <code>null</code>). 455: * 456: * @see #setShapesVisible(Boolean) 457: * 458: * @deprecated As of 1.0.7, use the per-series and base level settings. 459: */ 460: public Boolean getShapesVisible() { 461: return this.shapesVisible; 462: } 463: 464: /** 465: * Sets the 'shapes visible' for ALL series and sends a 466: * {@link RendererChangeEvent} to all registered listeners. 467: * 468: * @param visible the flag (<code>null</code> permitted). 469: * 470: * @see #getShapesVisible() 471: * 472: * @deprecated As of 1.0.7, use the per-series and base level settings. 473: */ 474: public void setShapesVisible(Boolean visible) { 475: this.shapesVisible = visible; 476: fireChangeEvent(); 477: } 478: 479: /** 480: * Sets the 'shapes visible' for ALL series and sends a 481: * {@link RendererChangeEvent} to all registered listeners. 482: * 483: * @param visible the flag. 484: * 485: * @see #getShapesVisible() 486: * 487: * @deprecated As of 1.0.7, use the per-series and base level settings. 488: */ 489: public void setShapesVisible(boolean visible) { 490: setShapesVisible(BooleanUtilities.valueOf(visible)); 491: } 492: 493: /** 494: * Returns the flag used to control whether or not the shapes for a series 495: * are visible. 496: * 497: * @param series the series index (zero-based). 498: * 499: * @return A boolean. 500: * 501: * @see #setSeriesShapesVisible(int, Boolean) 502: */ 503: public Boolean getSeriesShapesVisible(int series) { 504: return this.seriesShapesVisible.getBoolean(series); 505: } 506: 507: /** 508: * Sets the 'shapes visible' flag for a series and sends a 509: * {@link RendererChangeEvent} to all registered listeners. 510: * 511: * @param series the series index (zero-based). 512: * @param visible the flag. 513: * 514: * @see #getSeriesShapesVisible(int) 515: */ 516: public void setSeriesShapesVisible(int series, boolean visible) { 517: setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible)); 518: } 519: 520: /** 521: * Sets the 'shapes visible' flag for a series and sends a 522: * {@link RendererChangeEvent} to all registered listeners. 523: * 524: * @param series the series index (zero-based). 525: * @param flag the flag. 526: * 527: * @see #getSeriesShapesVisible(int) 528: */ 529: public void setSeriesShapesVisible(int series, Boolean flag) { 530: this.seriesShapesVisible.setBoolean(series, flag); 531: fireChangeEvent(); 532: } 533: 534: /** 535: * Returns the base 'shape visible' attribute. 536: * 537: * @return The base flag. 538: * 539: * @see #setBaseShapesVisible(boolean) 540: */ 541: public boolean getBaseShapesVisible() { 542: return this.baseShapesVisible; 543: } 544: 545: /** 546: * Sets the base 'shapes visible' flag and sends a 547: * {@link RendererChangeEvent} to all registered listeners. 548: * 549: * @param flag the flag. 550: * 551: * @see #getBaseShapesVisible() 552: */ 553: public void setBaseShapesVisible(boolean flag) { 554: this.baseShapesVisible = flag; 555: fireChangeEvent(); 556: } 557: 558: // SHAPES FILLED 559: 560: /** 561: * Returns the flag used to control whether or not the shape for an item 562: * is filled. 563: * <p> 564: * The default implementation passes control to the 565: * <code>getSeriesShapesFilled</code> method. You can override this method 566: * if you require different behaviour. 567: * 568: * @param series the series index (zero-based). 569: * @param item the item index (zero-based). 570: * 571: * @return A boolean. 572: */ 573: public boolean getItemShapeFilled(int series, int item) { 574: Boolean flag = this.shapesFilled; 575: if (flag == null) { 576: flag = getSeriesShapesFilled(series); 577: } 578: if (flag != null) { 579: return flag.booleanValue(); 580: } 581: else { 582: return this.baseShapesFilled; 583: } 584: } 585: 586: /** 587: * Sets the 'shapes filled' for ALL series and sends a 588: * {@link RendererChangeEvent} to all registered listeners. 589: * 590: * @param filled the flag. 591: * 592: * @deprecated As of 1.0.7, use the per-series and base level settings. 593: */ 594: public void setShapesFilled(boolean filled) { 595: setShapesFilled(BooleanUtilities.valueOf(filled)); 596: } 597: 598: /** 599: * Sets the 'shapes filled' for ALL series and sends a 600: * {@link RendererChangeEvent} to all registered listeners. 601: * 602: * @param filled the flag (<code>null</code> permitted). 603: * 604: * @deprecated As of 1.0.7, use the per-series and base level settings. 605: */ 606: public void setShapesFilled(Boolean filled) { 607: this.shapesFilled = filled; 608: fireChangeEvent(); 609: } 610: 611: /** 612: * Returns the flag used to control whether or not the shapes for a series 613: * are filled. 614: * 615: * @param series the series index (zero-based). 616: * 617: * @return A boolean. 618: * 619: * @see #setSeriesShapesFilled(int, Boolean) 620: */ 621: public Boolean getSeriesShapesFilled(int series) { 622: return this.seriesShapesFilled.getBoolean(series); 623: } 624: 625: /** 626: * Sets the 'shapes filled' flag for a series and sends a 627: * {@link RendererChangeEvent} to all registered listeners. 628: * 629: * @param series the series index (zero-based). 630: * @param flag the flag. 631: * 632: * @see #getSeriesShapesFilled(int) 633: */ 634: public void setSeriesShapesFilled(int series, boolean flag) { 635: setSeriesShapesFilled(series, BooleanUtilities.valueOf(flag)); 636: } 637: 638: /** 639: * Sets the 'shapes filled' flag for a series and sends a 640: * {@link RendererChangeEvent} to all registered listeners. 641: * 642: * @param series the series index (zero-based). 643: * @param flag the flag. 644: * 645: * @see #getSeriesShapesFilled(int) 646: */ 647: public void setSeriesShapesFilled(int series, Boolean flag) { 648: this.seriesShapesFilled.setBoolean(series, flag); 649: fireChangeEvent(); 650: } 651: 652: /** 653: * Returns the base 'shape filled' attribute. 654: * 655: * @return The base flag. 656: * 657: * @see #setBaseShapesFilled(boolean) 658: */ 659: public boolean getBaseShapesFilled() { 660: return this.baseShapesFilled; 661: } 662: 663: /** 664: * Sets the base 'shapes filled' flag and sends a 665: * {@link RendererChangeEvent} to all registered listeners. 666: * 667: * @param flag the flag. 668: * 669: * @see #getBaseShapesFilled() 670: */ 671: public void setBaseShapesFilled(boolean flag) { 672: this.baseShapesFilled = flag; 673: fireChangeEvent(); 674: } 675: 676: /** 677: * Returns <code>true</code> if outlines should be drawn for shapes, and 678: * <code>false</code> otherwise. 679: * 680: * @return A boolean. 681: * 682: * @see #setDrawOutlines(boolean) 683: */ 684: public boolean getDrawOutlines() { 685: return this.drawOutlines; 686: } 687: 688: /** 689: * Sets the flag that controls whether outlines are drawn for 690: * shapes, and sends a {@link RendererChangeEvent} to all registered 691: * listeners. 692: * <P> 693: * In some cases, shapes look better if they do NOT have an outline, but 694: * this flag allows you to set your own preference. 695: * 696: * @param flag the flag. 697: * 698: * @see #getDrawOutlines() 699: */ 700: public void setDrawOutlines(boolean flag) { 701: this.drawOutlines = flag; 702: fireChangeEvent(); 703: } 704: 705: /** 706: * Returns <code>true</code> if the renderer should use the fill paint 707: * setting to fill shapes, and <code>false</code> if it should just 708: * use the regular paint. 709: * <p> 710: * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the 711: * effect of this flag. 712: * 713: * @return A boolean. 714: * 715: * @see #setUseFillPaint(boolean) 716: * @see #getUseOutlinePaint() 717: */ 718: public boolean getUseFillPaint() { 719: return this.useFillPaint; 720: } 721: 722: /** 723: * Sets the flag that controls whether the fill paint is used to fill 724: * shapes, and sends a {@link RendererChangeEvent} to all 725: * registered listeners. 726: * 727: * @param flag the flag. 728: * 729: * @see #getUseFillPaint() 730: */ 731: public void setUseFillPaint(boolean flag) { 732: this.useFillPaint = flag; 733: fireChangeEvent(); 734: } 735: 736: /** 737: * Returns <code>true</code> if the renderer should use the outline paint 738: * setting to draw shape outlines, and <code>false</code> if it should just 739: * use the regular paint. 740: * 741: * @return A boolean. 742: * 743: * @see #setUseOutlinePaint(boolean) 744: * @see #getUseFillPaint() 745: */ 746: public boolean getUseOutlinePaint() { 747: return this.useOutlinePaint; 748: } 749: 750: /** 751: * Sets the flag that controls whether the outline paint is used to draw 752: * shape outlines, and sends a {@link RendererChangeEvent} to all 753: * registered listeners. 754: * <p> 755: * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the 756: * effect of this flag. 757: * 758: * @param flag the flag. 759: * 760: * @see #getUseOutlinePaint() 761: */ 762: public void setUseOutlinePaint(boolean flag) { 763: this.useOutlinePaint = flag; 764: fireChangeEvent(); 765: } 766: 767: /** 768: * Records the state for the renderer. This is used to preserve state 769: * information between calls to the drawItem() method for a single chart 770: * drawing. 771: */ 772: public static class State extends XYItemRendererState { 773: 774: /** The path for the current series. */ 775: public GeneralPath seriesPath; 776: 777: /** 778: * A flag that indicates if the last (x, y) point was 'good' 779: * (non-null). 780: */ 781: private boolean lastPointGood; 782: 783: /** 784: * Creates a new state instance. 785: * 786: * @param info the plot rendering info. 787: */ 788: public State(PlotRenderingInfo info) { 789: super(info); 790: } 791: 792: /** 793: * Returns a flag that indicates if the last point drawn (in the 794: * current series) was 'good' (non-null). 795: * 796: * @return A boolean. 797: */ 798: public boolean isLastPointGood() { 799: return this.lastPointGood; 800: } 801: 802: /** 803: * Sets a flag that indicates if the last point drawn (in the current 804: * series) was 'good' (non-null). 805: * 806: * @param good the flag. 807: */ 808: public void setLastPointGood(boolean good) { 809: this.lastPointGood = good; 810: } 811: } 812: 813: /** 814: * Initialises the renderer. 815: * <P> 816: * This method will be called before the first item is rendered, giving the 817: * renderer an opportunity to initialise any state information it wants to 818: * maintain. The renderer can do nothing if it chooses. 819: * 820: * @param g2 the graphics device. 821: * @param dataArea the area inside the axes. 822: * @param plot the plot. 823: * @param data the data. 824: * @param info an optional info collection object to return data back to 825: * the caller. 826: * 827: * @return The renderer state. 828: */ 829: public XYItemRendererState initialise(Graphics2D g2, 830: Rectangle2D dataArea, 831: XYPlot plot, 832: XYDataset data, 833: PlotRenderingInfo info) { 834: 835: State state = new State(info); 836: state.seriesPath = new GeneralPath(); 837: return state; 838: 839: } 840: 841: /** 842: * Draws the visual representation of a single data item. 843: * 844: * @param g2 the graphics device. 845: * @param state the renderer state. 846: * @param dataArea the area within which the data is being drawn. 847: * @param info collects information about the drawing. 848: * @param plot the plot (can be used to obtain standard color 849: * information etc). 850: * @param domainAxis the domain axis. 851: * @param rangeAxis the range axis. 852: * @param dataset the dataset. 853: * @param series the series index (zero-based). 854: * @param item the item index (zero-based). 855: * @param crosshairState crosshair information for the plot 856: * (<code>null</code> permitted). 857: * @param pass the pass index. 858: */ 859: public void drawItem(Graphics2D g2, 860: XYItemRendererState state, 861: Rectangle2D dataArea, 862: PlotRenderingInfo info, 863: XYPlot plot, 864: ValueAxis domainAxis, 865: ValueAxis rangeAxis, 866: XYDataset dataset, 867: int series, 868: int item, 869: CrosshairState crosshairState, 870: int pass) { 871: 872: // do nothing if item is not visible 873: if (!getItemVisible(series, item)) { 874: return; 875: } 876: 877: // first pass draws the background (lines, for instance) 878: if (isLinePass(pass)) { 879: if (item == 0) { 880: if (this.drawSeriesLineAsPath) { 881: State s = (State) state; 882: s.seriesPath.reset(); 883: s.lastPointGood = false; 884: } 885: } 886: 887: if (getItemLineVisible(series, item)) { 888: if (this.drawSeriesLineAsPath) { 889: drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 890: series, item, domainAxis, rangeAxis, dataArea); 891: } 892: else { 893: drawPrimaryLine(state, g2, plot, dataset, pass, series, 894: item, domainAxis, rangeAxis, dataArea); 895: } 896: } 897: } 898: // second pass adds shapes where the items are .. 899: else if (isItemPass(pass)) { 900: 901: // setup for collecting optional entity info... 902: EntityCollection entities = null; 903: if (info != null) { 904: entities = info.getOwner().getEntityCollection(); 905: } 906: 907: drawSecondaryPass(g2, plot, dataset, pass, series, item, 908: domainAxis, dataArea, rangeAxis, crosshairState, entities); 909: } 910: } 911: 912: /** 913: * Returns <code>true</code> if the specified pass is the one for drawing 914: * lines. 915: * 916: * @param pass the pass. 917: * 918: * @return A boolean. 919: */ 920: protected boolean isLinePass(int pass) { 921: return pass == 0; 922: } 923: 924: /** 925: * Returns <code>true</code> if the specified pass is the one for drawing 926: * items. 927: * 928: * @param pass the pass. 929: * 930: * @return A boolean. 931: */ 932: protected boolean isItemPass(int pass) { 933: return pass == 1; 934: } 935: 936: /** 937: * Draws the item (first pass). This method draws the lines 938: * connecting the items. 939: * 940: * @param g2 the graphics device. 941: * @param state the renderer state. 942: * @param dataArea the area within which the data is being drawn. 943: * @param plot the plot (can be used to obtain standard color 944: * information etc). 945: * @param domainAxis the domain axis. 946: * @param rangeAxis the range axis. 947: * @param dataset the dataset. 948: * @param pass the pass. 949: * @param series the series index (zero-based). 950: * @param item the item index (zero-based). 951: */ 952: protected void drawPrimaryLine(XYItemRendererState state, 953: Graphics2D g2, 954: XYPlot plot, 955: XYDataset dataset, 956: int pass, 957: int series, 958: int item, 959: ValueAxis domainAxis, 960: ValueAxis rangeAxis, 961: Rectangle2D dataArea) { 962: if (item == 0) { 963: return; 964: } 965: 966: // get the data point... 967: double x1 = dataset.getXValue(series, item); 968: double y1 = dataset.getYValue(series, item); 969: if (Double.isNaN(y1) || Double.isNaN(x1)) { 970: return; 971: } 972: 973: double x0 = dataset.getXValue(series, item - 1); 974: double y0 = dataset.getYValue(series, item - 1); 975: if (Double.isNaN(y0) || Double.isNaN(x0)) { 976: return; 977: } 978: 979: RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 980: RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 981: 982: double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation); 983: double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation); 984: 985: double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 986: double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 987: 988: // only draw if we have good values 989: if (Double.isNaN(transX0) || Double.isNaN(transY0) 990: || Double.isNaN(transX1) || Double.isNaN(transY1)) { 991: return; 992: } 993: 994: PlotOrientation orientation = plot.getOrientation(); 995: if (orientation == PlotOrientation.HORIZONTAL) { 996: state.workingLine.setLine(transY0, transX0, transY1, transX1); 997: } 998: else if (orientation == PlotOrientation.VERTICAL) { 999: state.workingLine.setLine(transX0, transY0, transX1, transY1); 1000: } 1001: 1002: if (state.workingLine.intersects(dataArea)) { 1003: drawFirstPassShape(g2, pass, series, item, state.workingLine); 1004: } 1005: } 1006: 1007: /** 1008: * Draws the first pass shape. 1009: * 1010: * @param g2 the graphics device. 1011: * @param pass the pass. 1012: * @param series the series index. 1013: * @param item the item index. 1014: * @param shape the shape. 1015: */ 1016: protected void drawFirstPassShape(Graphics2D g2, int pass, int series, 1017: int item, Shape shape) { 1018: g2.setStroke(getItemStroke(series, item)); 1019: g2.setPaint(getItemPaint(series, item)); 1020: g2.draw(shape); 1021: } 1022: 1023: 1024: /** 1025: * Draws the item (first pass). This method draws the lines 1026: * connecting the items. Instead of drawing separate lines, 1027: * a GeneralPath is constructed and drawn at the end of 1028: * the series painting. 1029: * 1030: * @param g2 the graphics device. 1031: * @param state the renderer state. 1032: * @param plot the plot (can be used to obtain standard color information 1033: * etc). 1034: * @param dataset the dataset. 1035: * @param pass the pass. 1036: * @param series the series index (zero-based). 1037: * @param item the item index (zero-based). 1038: * @param domainAxis the domain axis. 1039: * @param rangeAxis the range axis. 1040: * @param dataArea the area within which the data is being drawn. 1041: */ 1042: protected void drawPrimaryLineAsPath(XYItemRendererState state, 1043: Graphics2D g2, XYPlot plot, 1044: XYDataset dataset, 1045: int pass, 1046: int series, 1047: int item, 1048: ValueAxis domainAxis, 1049: ValueAxis rangeAxis, 1050: Rectangle2D dataArea) { 1051: 1052: 1053: RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1054: RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1055: 1056: // get the data point... 1057: double x1 = dataset.getXValue(series, item); 1058: double y1 = dataset.getYValue(series, item); 1059: double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1060: double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1061: 1062: State s = (State) state; 1063: // update path to reflect latest point 1064: if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 1065: float x = (float) transX1; 1066: float y = (float) transY1; 1067: PlotOrientation orientation = plot.getOrientation(); 1068: if (orientation == PlotOrientation.HORIZONTAL) { 1069: x = (float) transY1; 1070: y = (float) transX1; 1071: } 1072: if (s.isLastPointGood()) { 1073: s.seriesPath.lineTo(x, y); 1074: } 1075: else { 1076: s.seriesPath.moveTo(x, y); 1077: } 1078: s.setLastPointGood(true); 1079: } 1080: else { 1081: s.setLastPointGood(false); 1082: } 1083: // if this is the last item, draw the path ... 1084: if (item == dataset.getItemCount(series) - 1) { 1085: // draw path 1086: drawFirstPassShape(g2, pass, series, item, s.seriesPath); 1087: } 1088: } 1089: 1090: /** 1091: * Draws the item shapes and adds chart entities (second pass). This method 1092: * draws the shapes which mark the item positions. If <code>entities</code> 1093: * is not <code>null</code> it will be populated with entity information 1094: * for points that fall within the data area. 1095: * 1096: * @param g2 the graphics device. 1097: * @param plot the plot (can be used to obtain standard color 1098: * information etc). 1099: * @param domainAxis the domain axis. 1100: * @param dataArea the area within which the data is being drawn. 1101: * @param rangeAxis the range axis. 1102: * @param dataset the dataset. 1103: * @param pass the pass. 1104: * @param series the series index (zero-based). 1105: * @param item the item index (zero-based). 1106: * @param crosshairState the crosshair state. 1107: * @param entities the entity collection. 1108: */ 1109: protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 1110: XYDataset dataset, 1111: int pass, int series, int item, 1112: ValueAxis domainAxis, 1113: Rectangle2D dataArea, 1114: ValueAxis rangeAxis, 1115: CrosshairState crosshairState, 1116: EntityCollection entities) { 1117: 1118: Shape entityArea = null; 1119: 1120: // get the data point... 1121: double x1 = dataset.getXValue(series, item); 1122: double y1 = dataset.getYValue(series, item); 1123: if (Double.isNaN(y1) || Double.isNaN(x1)) { 1124: return; 1125: } 1126: 1127: PlotOrientation orientation = plot.getOrientation(); 1128: RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1129: RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1130: double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1131: double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1132: 1133: if (getItemShapeVisible(series, item)) { 1134: Shape shape = getItemShape(series, item); 1135: if (orientation == PlotOrientation.HORIZONTAL) { 1136: shape = ShapeUtilities.createTranslatedShape(shape, transY1, 1137: transX1); 1138: } 1139: else if (orientation == PlotOrientation.VERTICAL) { 1140: shape = ShapeUtilities.createTranslatedShape(shape, transX1, 1141: transY1); 1142: } 1143: entityArea = shape; 1144: if (shape.intersects(dataArea)) { 1145: if (getItemShapeFilled(series, item)) { 1146: if (this.useFillPaint) { 1147: g2.setPaint(getItemFillPaint(series, item)); 1148: } 1149: else { 1150: g2.setPaint(getItemPaint(series, item)); 1151: } 1152: g2.fill(shape); 1153: } 1154: if (this.drawOutlines) { 1155: if (getUseOutlinePaint()) { 1156: g2.setPaint(getItemOutlinePaint(series, item)); 1157: } 1158: else { 1159: g2.setPaint(getItemPaint(series, item)); 1160: } 1161: g2.setStroke(getItemOutlineStroke(series, item)); 1162: g2.draw(shape); 1163: } 1164: } 1165: } 1166: 1167: double xx = transX1; 1168: double yy = transY1; 1169: if (orientation == PlotOrientation.HORIZONTAL) { 1170: xx = transY1; 1171: yy = transX1; 1172: } 1173: 1174: // draw the item label if there is one... 1175: if (isItemLabelVisible(series, item)) { 1176: drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 1177: (y1 < 0.0)); 1178: } 1179: 1180: int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 1181: int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 1182: updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 1183: rangeAxisIndex, transX1, transY1, plot.getOrientation()); 1184: 1185: // add an entity for the item, but only if it falls within the data 1186: // area... 1187: if (entities != null && dataArea.contains(xx, yy)) { 1188: addEntity(entities, entityArea, dataset, series, item, xx, yy); 1189: } 1190: } 1191: 1192: 1193: /** 1194: * Returns a legend item for the specified series. 1195: * 1196: * @param datasetIndex the dataset index (zero-based). 1197: * @param series the series index (zero-based). 1198: * 1199: * @return A legend item for the series. 1200: */ 1201: public LegendItem getLegendItem(int datasetIndex, int series) { 1202: 1203: XYPlot plot = getPlot(); 1204: if (plot == null) { 1205: return null; 1206: } 1207: 1208: LegendItem result = null; 1209: XYDataset dataset = plot.getDataset(datasetIndex); 1210: if (dataset != null) { 1211: if (getItemVisible(series, 0)) { 1212: String label = getLegendItemLabelGenerator().generateLabel( 1213: dataset, series); 1214: String description = label; 1215: String toolTipText = null; 1216: if (getLegendItemToolTipGenerator() != null) { 1217: toolTipText = getLegendItemToolTipGenerator().generateLabel( 1218: dataset, series); 1219: } 1220: String urlText = null; 1221: if (getLegendItemURLGenerator() != null) { 1222: urlText = getLegendItemURLGenerator().generateLabel( 1223: dataset, series); 1224: } 1225: boolean shapeIsVisible = getItemShapeVisible(series, 0); 1226: Shape shape = lookupSeriesShape(series); 1227: boolean shapeIsFilled = getItemShapeFilled(series, 0); 1228: Paint fillPaint = (this.useFillPaint 1229: ? lookupSeriesFillPaint(series) 1230: : lookupSeriesPaint(series)); 1231: boolean shapeOutlineVisible = this.drawOutlines; 1232: Paint outlinePaint = (this.useOutlinePaint 1233: ? lookupSeriesOutlinePaint(series) 1234: : lookupSeriesPaint(series)); 1235: Stroke outlineStroke = lookupSeriesOutlineStroke(series); 1236: boolean lineVisible = getItemLineVisible(series, 0); 1237: Stroke lineStroke = lookupSeriesStroke(series); 1238: Paint linePaint = lookupSeriesPaint(series); 1239: result = new LegendItem(label, description, toolTipText, 1240: urlText, shapeIsVisible, shape, shapeIsFilled, 1241: fillPaint, shapeOutlineVisible, outlinePaint, 1242: outlineStroke, lineVisible, this.legendLine, 1243: lineStroke, linePaint); 1244: result.setSeriesKey(dataset.getSeriesKey(series)); 1245: result.setSeriesIndex(series); 1246: result.setDataset(dataset); 1247: result.setDatasetIndex(datasetIndex); 1248: } 1249: } 1250: 1251: return result; 1252: 1253: } 1254: 1255: /** 1256: * Returns a clone of the renderer. 1257: * 1258: * @return A clone. 1259: * 1260: * @throws CloneNotSupportedException if the clone cannot be created. 1261: */ 1262: public Object clone() throws CloneNotSupportedException { 1263: XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone(); 1264: clone.seriesLinesVisible 1265: = (BooleanList) this.seriesLinesVisible.clone(); 1266: if (this.legendLine != null) { 1267: clone.legendLine = ShapeUtilities.clone(this.legendLine); 1268: } 1269: clone.seriesShapesVisible 1270: = (BooleanList) this.seriesShapesVisible.clone(); 1271: clone.seriesShapesFilled 1272: = (BooleanList) this.seriesShapesFilled.clone(); 1273: return clone; 1274: } 1275: 1276: /** 1277: * Tests this renderer for equality with an arbitrary object. 1278: * 1279: * @param obj the object (<code>null</code> permitted). 1280: * 1281: * @return <code>true</code> or <code>false</code>. 1282: */ 1283: public boolean equals(Object obj) { 1284: 1285: if (obj == this) { 1286: return true; 1287: } 1288: if (!(obj instanceof XYLineAndShapeRenderer)) { 1289: return false; 1290: } 1291: if (!super.equals(obj)) { 1292: return false; 1293: } 1294: XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj; 1295: if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) { 1296: return false; 1297: } 1298: if (!ObjectUtilities.equal( 1299: this.seriesLinesVisible, that.seriesLinesVisible) 1300: ) { 1301: return false; 1302: } 1303: if (this.baseLinesVisible != that.baseLinesVisible) { 1304: return false; 1305: } 1306: if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 1307: return false; 1308: } 1309: if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) { 1310: return false; 1311: } 1312: if (!ObjectUtilities.equal( 1313: this.seriesShapesVisible, that.seriesShapesVisible) 1314: ) { 1315: return false; 1316: } 1317: if (this.baseShapesVisible != that.baseShapesVisible) { 1318: return false; 1319: } 1320: if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1321: return false; 1322: } 1323: if (!ObjectUtilities.equal( 1324: this.seriesShapesFilled, that.seriesShapesFilled) 1325: ) { 1326: return false; 1327: } 1328: if (this.baseShapesFilled != that.baseShapesFilled) { 1329: return false; 1330: } 1331: if (this.drawOutlines != that.drawOutlines) { 1332: return false; 1333: } 1334: if (this.useOutlinePaint != that.useOutlinePaint) { 1335: return false; 1336: } 1337: if (this.useFillPaint != that.useFillPaint) { 1338: return false; 1339: } 1340: if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) { 1341: return false; 1342: } 1343: return true; 1344: 1345: } 1346: 1347: /** 1348: * Provides serialization support. 1349: * 1350: * @param stream the input stream. 1351: * 1352: * @throws IOException if there is an I/O error. 1353: * @throws ClassNotFoundException if there is a classpath problem. 1354: */ 1355: private void readObject(ObjectInputStream stream) 1356: throws IOException, ClassNotFoundException { 1357: stream.defaultReadObject(); 1358: this.legendLine = SerialUtilities.readShape(stream); 1359: } 1360: 1361: /** 1362: * Provides serialization support. 1363: * 1364: * @param stream the output stream. 1365: * 1366: * @throws IOException if there is an I/O error. 1367: */ 1368: private void writeObject(ObjectOutputStream stream) throws IOException { 1369: stream.defaultWriteObject(); 1370: SerialUtilities.writeShape(this.legendLine, stream); 1371: } 1372: 1373: }