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