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