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