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: * LineAndShapeRenderer.java 29: * ------------------------- 30: * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Mark Watson (www.markwatson.com); 34: * Jeremy Bowman; 35: * Richard Atkinson; 36: * Christian W. Zuckschwerdt; 37: * 38: * Changes 39: * ------- 40: * 23-Oct-2001 : Version 1 (DG); 41: * 15-Nov-2001 : Modified to allow for null data values (DG); 42: * 16-Jan-2002 : Renamed HorizontalCategoryItemRenderer.java 43: * --> CategoryItemRenderer.java (DG); 44: * 05-Feb-2002 : Changed return type of the drawCategoryItem method from void 45: * to Shape, as part of the tooltips implementation (DG); 46: * 11-May-2002 : Support for value label drawing (JB); 47: * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG); 48: * 25-Jun-2002 : Removed redundant import (DG); 49: * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 50: * for HTML image maps (RA); 51: * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); 52: * 11-Oct-2002 : Added new constructor to incorporate tool tip and URL 53: * generators (DG); 54: * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 55: * CategoryToolTipGenerator interface (DG); 56: * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG); 57: * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis 58: * for category spacing (DG); 59: * 17-Jan-2003 : Moved plot classes to a separate package (DG); 60: * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem() 61: * method (DG); 62: * 12-May-2003 : Modified to take into account the plot orientation (DG); 63: * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG); 64: * 30-Jul-2003 : Modified entity constructor (CZ); 65: * 22-Sep-2003 : Fixed cloning (DG); 66: * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 67: * override easier (DG); 68: * 16-Jun-2004 : Fixed bug (id=972454) with label positioning on horizontal 69: * charts (DG); 70: * 15-Oct-2004 : Updated equals() method (DG); 71: * 05-Nov-2004 : Modified drawItem() signature (DG); 72: * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG); 73: * 27-Jan-2005 : Changed attribute names, modified constructor and removed 74: * constants (DG); 75: * 01-Feb-2005 : Removed unnecessary constants (DG); 76: * 15-Mar-2005 : Fixed bug 1163897, concerning outlines for shapes (DG); 77: * 13-Apr-2005 : Check flags that control series visibility (DG); 78: * 20-Apr-2005 : Use generators for legend labels, tooltips and URLs (DG); 79: * 09-Jun-2005 : Use addItemEntity() method (DG); 80: * ------------- JFREECHART 1.0.x --------------------------------------------- 81: * 25-May-2006 : Added check to drawItem() to detect when both the line and 82: * the shape are not visible (DG); 83: * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 84: * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG); 85: * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 86: * 24-Sep-2007 : Deprecated redundant fields/methods (DG); 87: * 27-Sep-2007 : Added option to offset series x-position within category (DG); 88: * 89: */ 90: 91: package org.jfree.chart.renderer.category; 92: 93: import java.awt.Graphics2D; 94: import java.awt.Paint; 95: import java.awt.Shape; 96: import java.awt.Stroke; 97: import java.awt.geom.Line2D; 98: import java.awt.geom.Rectangle2D; 99: import java.io.Serializable; 100: 101: import org.jfree.chart.LegendItem; 102: import org.jfree.chart.axis.CategoryAxis; 103: import org.jfree.chart.axis.ValueAxis; 104: import org.jfree.chart.entity.EntityCollection; 105: import org.jfree.chart.event.RendererChangeEvent; 106: import org.jfree.chart.plot.CategoryPlot; 107: import org.jfree.chart.plot.PlotOrientation; 108: import org.jfree.data.category.CategoryDataset; 109: import org.jfree.util.BooleanList; 110: import org.jfree.util.BooleanUtilities; 111: import org.jfree.util.ObjectUtilities; 112: import org.jfree.util.PublicCloneable; 113: import org.jfree.util.ShapeUtilities; 114: 115: /** 116: * A renderer that draws shapes for each data item, and lines between data 117: * items (for use with the {@link CategoryPlot} class). 118: */ 119: public class LineAndShapeRenderer extends AbstractCategoryItemRenderer 120: implements Cloneable, PublicCloneable, Serializable { 121: 122: /** For serialization. */ 123: private static final long serialVersionUID = -197749519869226398L; 124: 125: /** 126: * A flag that controls whether or not lines are visible for ALL series. 127: * 128: * @deprecated As of 1.0.7 (this override flag is unnecessary). 129: */ 130: private Boolean linesVisible; 131: 132: /** 133: * A table of flags that control (per series) whether or not lines are 134: * visible. 135: */ 136: private BooleanList seriesLinesVisible; 137: 138: /** 139: * A flag indicating whether or not lines are drawn between non-null 140: * points. 141: */ 142: private boolean baseLinesVisible; 143: 144: /** 145: * A flag that controls whether or not shapes are visible for ALL series. 146: * 147: * @deprecated As of 1.0.7 (this override flag is unnecessary). 148: */ 149: private Boolean shapesVisible; 150: 151: /** 152: * A table of flags that control (per series) whether or not shapes are 153: * visible. 154: */ 155: private BooleanList seriesShapesVisible; 156: 157: /** The default value returned by the getShapeVisible() method. */ 158: private boolean baseShapesVisible; 159: 160: /** 161: * A flag that controls whether or not shapes are filled for ALL series. 162: * 163: * @deprecated As of 1.0.7 (this override flag is unnecessary). 164: */ 165: private Boolean shapesFilled; 166: 167: /** 168: * A table of flags that control (per series) whether or not shapes are 169: * filled. 170: */ 171: private BooleanList seriesShapesFilled; 172: 173: /** The default value returned by the getShapeFilled() method. */ 174: private boolean baseShapesFilled; 175: 176: /** 177: * A flag that controls whether the fill paint is used for filling 178: * shapes. 179: */ 180: private boolean useFillPaint; 181: 182: /** A flag that controls whether outlines are drawn for shapes. */ 183: private boolean drawOutlines; 184: 185: /** 186: * A flag that controls whether the outline paint is used for drawing shape 187: * outlines - if not, the regular series paint is used. 188: */ 189: private boolean useOutlinePaint; 190: 191: /** 192: * A flag that controls whether or not the x-position for each item is 193: * offset within the category according to the series. 194: * 195: * @since 1.0.7 196: */ 197: private boolean useSeriesOffset; 198: 199: /** 200: * The item margin used for series offsetting - this allows the positioning 201: * to match the bar positions of the {@link BarRenderer} class. 202: * 203: * @since 1.0.7 204: */ 205: private double itemMargin; 206: 207: /** 208: * Creates a renderer with both lines and shapes visible by default. 209: */ 210: public LineAndShapeRenderer() { 211: this(true, true); 212: } 213: 214: /** 215: * Creates a new renderer with lines and/or shapes visible. 216: * 217: * @param lines draw lines? 218: * @param shapes draw shapes? 219: */ 220: public LineAndShapeRenderer(boolean lines, boolean shapes) { 221: super(); 222: this.linesVisible = null; 223: this.seriesLinesVisible = new BooleanList(); 224: this.baseLinesVisible = lines; 225: this.shapesVisible = null; 226: this.seriesShapesVisible = new BooleanList(); 227: this.baseShapesVisible = shapes; 228: this.shapesFilled = null; 229: this.seriesShapesFilled = new BooleanList(); 230: this.baseShapesFilled = true; 231: this.useFillPaint = false; 232: this.drawOutlines = true; 233: this.useOutlinePaint = false; 234: this.useSeriesOffset = false; // preserves old behaviour 235: this.itemMargin = 0.0; 236: } 237: 238: // LINES VISIBLE 239: 240: /** 241: * Returns the flag used to control whether or not the line for an item is 242: * visible. 243: * 244: * @param series the series index (zero-based). 245: * @param item the item index (zero-based). 246: * 247: * @return A boolean. 248: */ 249: public boolean getItemLineVisible(int series, int item) { 250: Boolean flag = this.linesVisible; 251: if (flag == null) { 252: flag = getSeriesLinesVisible(series); 253: } 254: if (flag != null) { 255: return flag.booleanValue(); 256: } 257: else { 258: return this.baseLinesVisible; 259: } 260: } 261: 262: /** 263: * Returns a flag that controls whether or not lines are drawn for ALL 264: * series. If this flag is <code>null</code>, then the "per series" 265: * settings will apply. 266: * 267: * @return A flag (possibly <code>null</code>). 268: * 269: * @see #setLinesVisible(Boolean) 270: * 271: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 272: * use the per-series and base (default) settings). 273: */ 274: public Boolean getLinesVisible() { 275: return this.linesVisible; 276: } 277: 278: /** 279: * Sets a flag that controls whether or not lines are drawn between the 280: * items in ALL series, and sends a {@link RendererChangeEvent} to all 281: * registered listeners. You need to set this to <code>null</code> if you 282: * want the "per series" settings to apply. 283: * 284: * @param visible the flag (<code>null</code> permitted). 285: * 286: * @see #getLinesVisible() 287: * 288: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 289: * use the per-series and base (default) settings). 290: */ 291: public void setLinesVisible(Boolean visible) { 292: this.linesVisible = visible; 293: fireChangeEvent(); 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. 300: * 301: * @param visible the flag. 302: * 303: * @see #getLinesVisible() 304: * 305: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 306: * use the per-series and base (default) settings). 307: */ 308: public void setLinesVisible(boolean visible) { 309: setLinesVisible(BooleanUtilities.valueOf(visible)); 310: } 311: 312: /** 313: * Returns the flag used to control whether or not the lines for a series 314: * are visible. 315: * 316: * @param series the series index (zero-based). 317: * 318: * @return The flag (possibly <code>null</code>). 319: * 320: * @see #setSeriesLinesVisible(int, Boolean) 321: */ 322: public Boolean getSeriesLinesVisible(int series) { 323: return this.seriesLinesVisible.getBoolean(series); 324: } 325: 326: /** 327: * Sets the 'lines visible' flag for a series and sends a 328: * {@link RendererChangeEvent} to all registered listeners. 329: * 330: * @param series the series index (zero-based). 331: * @param flag the flag (<code>null</code> permitted). 332: * 333: * @see #getSeriesLinesVisible(int) 334: */ 335: public void setSeriesLinesVisible(int series, Boolean flag) { 336: this.seriesLinesVisible.setBoolean(series, flag); 337: fireChangeEvent(); 338: } 339: 340: /** 341: * Sets the 'lines visible' flag for a series and sends a 342: * {@link RendererChangeEvent} to all registered listeners. 343: * 344: * @param series the series index (zero-based). 345: * @param visible the flag. 346: * 347: * @see #getSeriesLinesVisible(int) 348: */ 349: public void setSeriesLinesVisible(int series, boolean visible) { 350: setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible)); 351: } 352: 353: /** 354: * Returns the base 'lines visible' attribute. 355: * 356: * @return The base flag. 357: * 358: * @see #getBaseLinesVisible() 359: */ 360: public boolean getBaseLinesVisible() { 361: return this.baseLinesVisible; 362: } 363: 364: /** 365: * Sets the base 'lines visible' flag and sends a 366: * {@link RendererChangeEvent} to all registered listeners. 367: * 368: * @param flag the flag. 369: * 370: * @see #getBaseLinesVisible() 371: */ 372: public void setBaseLinesVisible(boolean flag) { 373: this.baseLinesVisible = flag; 374: fireChangeEvent(); 375: } 376: 377: // SHAPES VISIBLE 378: 379: /** 380: * Returns the flag used to control whether or not the shape for an item is 381: * visible. 382: * 383: * @param series the series index (zero-based). 384: * @param item the item index (zero-based). 385: * 386: * @return A boolean. 387: */ 388: public boolean getItemShapeVisible(int series, int item) { 389: Boolean flag = this.shapesVisible; 390: if (flag == null) { 391: flag = getSeriesShapesVisible(series); 392: } 393: if (flag != null) { 394: return flag.booleanValue(); 395: } 396: else { 397: return this.baseShapesVisible; 398: } 399: } 400: 401: /** 402: * Returns the flag that controls whether the shapes are visible for the 403: * items in ALL series. 404: * 405: * @return The flag (possibly <code>null</code>). 406: * 407: * @see #setShapesVisible(Boolean) 408: * 409: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 410: * use the per-series and base (default) settings). 411: */ 412: public Boolean getShapesVisible() { 413: return this.shapesVisible; 414: } 415: 416: /** 417: * Sets the 'shapes visible' for ALL series and sends a 418: * {@link RendererChangeEvent} to all registered listeners. 419: * 420: * @param visible the flag (<code>null</code> permitted). 421: * 422: * @see #getShapesVisible() 423: * 424: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 425: * use the per-series and base (default) settings). 426: */ 427: public void setShapesVisible(Boolean visible) { 428: this.shapesVisible = visible; 429: fireChangeEvent(); 430: } 431: 432: /** 433: * Sets the 'shapes visible' for ALL series and sends a 434: * {@link RendererChangeEvent} to all registered listeners. 435: * 436: * @param visible the flag. 437: * 438: * @see #getShapesVisible() 439: * 440: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 441: * use the per-series and base (default) settings). 442: */ 443: public void setShapesVisible(boolean visible) { 444: setShapesVisible(BooleanUtilities.valueOf(visible)); 445: } 446: 447: /** 448: * Returns the flag used to control whether or not the shapes for a series 449: * are visible. 450: * 451: * @param series the series index (zero-based). 452: * 453: * @return A boolean. 454: * 455: * @see #setSeriesShapesVisible(int, Boolean) 456: */ 457: public Boolean getSeriesShapesVisible(int series) { 458: return this.seriesShapesVisible.getBoolean(series); 459: } 460: 461: /** 462: * Sets the 'shapes visible' flag for a series and sends a 463: * {@link RendererChangeEvent} to all registered listeners. 464: * 465: * @param series the series index (zero-based). 466: * @param visible the flag. 467: * 468: * @see #getSeriesShapesVisible(int) 469: */ 470: public void setSeriesShapesVisible(int series, boolean visible) { 471: setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible)); 472: } 473: 474: /** 475: * Sets the 'shapes visible' flag for a series and sends a 476: * {@link RendererChangeEvent} to all registered listeners. 477: * 478: * @param series the series index (zero-based). 479: * @param flag the flag. 480: * 481: * @see #getSeriesShapesVisible(int) 482: */ 483: public void setSeriesShapesVisible(int series, Boolean flag) { 484: this.seriesShapesVisible.setBoolean(series, flag); 485: fireChangeEvent(); 486: } 487: 488: /** 489: * Returns the base 'shape visible' attribute. 490: * 491: * @return The base flag. 492: * 493: * @see #setBaseShapesVisible(boolean) 494: */ 495: public boolean getBaseShapesVisible() { 496: return this.baseShapesVisible; 497: } 498: 499: /** 500: * Sets the base 'shapes visible' flag and sends a 501: * {@link RendererChangeEvent} to all registered listeners. 502: * 503: * @param flag the flag. 504: * 505: * @see #getBaseShapesVisible() 506: */ 507: public void setBaseShapesVisible(boolean flag) { 508: this.baseShapesVisible = flag; 509: fireChangeEvent(); 510: } 511: 512: /** 513: * Returns <code>true</code> if outlines should be drawn for shapes, and 514: * <code>false</code> otherwise. 515: * 516: * @return A boolean. 517: * 518: * @see #setDrawOutlines(boolean) 519: */ 520: public boolean getDrawOutlines() { 521: return this.drawOutlines; 522: } 523: 524: /** 525: * Sets the flag that controls whether outlines are drawn for 526: * shapes, and sends a {@link RendererChangeEvent} to all registered 527: * listeners. 528: * <P> 529: * In some cases, shapes look better if they do NOT have an outline, but 530: * this flag allows you to set your own preference. 531: * 532: * @param flag the flag. 533: * 534: * @see #getDrawOutlines() 535: */ 536: public void setDrawOutlines(boolean flag) { 537: this.drawOutlines = flag; 538: fireChangeEvent(); 539: } 540: 541: /** 542: * Returns the flag that controls whether the outline paint is used for 543: * shape outlines. If not, the regular series paint is used. 544: * 545: * @return A boolean. 546: * 547: * @see #setUseOutlinePaint(boolean) 548: */ 549: public boolean getUseOutlinePaint() { 550: return this.useOutlinePaint; 551: } 552: 553: /** 554: * Sets the flag that controls whether the outline paint is used for shape 555: * outlines, and sends a {@link RendererChangeEvent} to all registered 556: * listeners. 557: * 558: * @param use the flag. 559: * 560: * @see #getUseOutlinePaint() 561: */ 562: public void setUseOutlinePaint(boolean use) { 563: this.useOutlinePaint = use; 564: fireChangeEvent(); 565: } 566: 567: // SHAPES FILLED 568: 569: /** 570: * Returns the flag used to control whether or not the shape for an item 571: * is filled. The default implementation passes control to the 572: * <code>getSeriesShapesFilled</code> method. You can override this method 573: * if you require different behaviour. 574: * 575: * @param series the series index (zero-based). 576: * @param item the item index (zero-based). 577: * 578: * @return A boolean. 579: */ 580: public boolean getItemShapeFilled(int series, int item) { 581: return getSeriesShapesFilled(series); 582: } 583: 584: /** 585: * Returns the flag used to control whether or not the shapes for a series 586: * are filled. 587: * 588: * @param series the series index (zero-based). 589: * 590: * @return A boolean. 591: */ 592: public boolean getSeriesShapesFilled(int series) { 593: 594: // return the overall setting, if there is one... 595: if (this.shapesFilled != null) { 596: return this.shapesFilled.booleanValue(); 597: } 598: 599: // otherwise look up the paint table 600: Boolean flag = this.seriesShapesFilled.getBoolean(series); 601: if (flag != null) { 602: return flag.booleanValue(); 603: } 604: else { 605: return this.baseShapesFilled; 606: } 607: 608: } 609: 610: /** 611: * Returns the flag that controls whether or not shapes are filled for 612: * ALL series. 613: * 614: * @return A Boolean. 615: * 616: * @see #setShapesFilled(Boolean) 617: * 618: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 619: * use the per-series and base (default) settings). 620: */ 621: public Boolean getShapesFilled() { 622: return this.shapesFilled; 623: } 624: 625: /** 626: * Sets the 'shapes filled' for ALL series and sends a 627: * {@link RendererChangeEvent} to all registered listeners. 628: * 629: * @param filled the flag. 630: * 631: * @see #getShapesFilled() 632: * 633: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 634: * use the per-series and base (default) settings). 635: */ 636: public void setShapesFilled(boolean filled) { 637: if (filled) { 638: setShapesFilled(Boolean.TRUE); 639: } 640: else { 641: setShapesFilled(Boolean.FALSE); 642: } 643: } 644: 645: /** 646: * Sets the 'shapes filled' for ALL series and sends a 647: * {@link RendererChangeEvent} to all registered listeners. 648: * 649: * @param filled the flag (<code>null</code> permitted). 650: * 651: * @see #getShapesFilled() 652: * 653: * @deprecated As of 1.0.7 (the override facility is unnecessary, just 654: * use the per-series and base (default) settings). 655: */ 656: public void setShapesFilled(Boolean filled) { 657: this.shapesFilled = filled; 658: fireChangeEvent(); 659: } 660: 661: /** 662: * Sets the 'shapes filled' flag for a series and sends a 663: * {@link RendererChangeEvent} to all registered listeners. 664: * 665: * @param series the series index (zero-based). 666: * @param filled the flag. 667: * 668: * @see #getSeriesShapesFilled(int) 669: */ 670: public void setSeriesShapesFilled(int series, Boolean filled) { 671: this.seriesShapesFilled.setBoolean(series, filled); 672: fireChangeEvent(); 673: } 674: 675: /** 676: * Sets the 'shapes filled' flag for a series and sends a 677: * {@link RendererChangeEvent} to all registered listeners. 678: * 679: * @param series the series index (zero-based). 680: * @param filled the flag. 681: * 682: * @see #getSeriesShapesFilled(int) 683: */ 684: public void setSeriesShapesFilled(int series, boolean filled) { 685: // delegate 686: setSeriesShapesFilled(series, BooleanUtilities.valueOf(filled)); 687: } 688: 689: /** 690: * Returns the base 'shape filled' attribute. 691: * 692: * @return The base flag. 693: * 694: * @see #setBaseShapesFilled(boolean) 695: */ 696: public boolean getBaseShapesFilled() { 697: return this.baseShapesFilled; 698: } 699: 700: /** 701: * Sets the base 'shapes filled' flag and sends a 702: * {@link RendererChangeEvent} to all registered listeners. 703: * 704: * @param flag the flag. 705: * 706: * @see #getBaseShapesFilled() 707: */ 708: public void setBaseShapesFilled(boolean flag) { 709: this.baseShapesFilled = flag; 710: fireChangeEvent(); 711: } 712: 713: /** 714: * Returns <code>true</code> if the renderer should use the fill paint 715: * setting to fill shapes, and <code>false</code> if it should just 716: * use the regular paint. 717: * 718: * @return A boolean. 719: * 720: * @see #setUseFillPaint(boolean) 721: */ 722: public boolean getUseFillPaint() { 723: return this.useFillPaint; 724: } 725: 726: /** 727: * Sets the flag that controls whether the fill paint is used to fill 728: * shapes, and sends a {@link RendererChangeEvent} to all 729: * registered listeners. 730: * 731: * @param flag the flag. 732: * 733: * @see #getUseFillPaint() 734: */ 735: public void setUseFillPaint(boolean flag) { 736: this.useFillPaint = flag; 737: fireChangeEvent(); 738: } 739: 740: /** 741: * Returns the flag that controls whether or not the x-position for each 742: * data item is offset within the category according to the series. 743: * 744: * @return A boolean. 745: * 746: * @see #setUseSeriesOffset(boolean) 747: * 748: * @since 1.0.7 749: */ 750: public boolean getUseSeriesOffset() { 751: return this.useSeriesOffset; 752: } 753: 754: /** 755: * Sets the flag that controls whether or not the x-position for each 756: * data item is offset within its category according to the series, and 757: * sends a {@link RendererChangeEvent} to all registered listeners. 758: * 759: * @param offset the offset. 760: * 761: * @see #getUseSeriesOffset() 762: * 763: * @since 1.0.7 764: */ 765: public void setUseSeriesOffset(boolean offset) { 766: this.useSeriesOffset = offset; 767: fireChangeEvent(); 768: } 769: 770: /** 771: * Returns the item margin, which is the gap between items within a 772: * category (expressed as a percentage of the overall category width). 773: * This can be used to match the offset alignment with the bars drawn by 774: * a {@link BarRenderer}). 775: * 776: * @return The item margin. 777: * 778: * @see #setItemMargin(double) 779: * @see #getUseSeriesOffset() 780: * 781: * @since 1.0.7 782: */ 783: public double getItemMargin() { 784: return this.itemMargin; 785: } 786: 787: /** 788: * Sets the item margin, which is the gap between items within a category 789: * (expressed as a percentage of the overall category width), and sends 790: * a {@link RendererChangeEvent} to all registered listeners. 791: * 792: * @param margin the margin (0.0 <= margin < 1.0). 793: * 794: * @see #getItemMargin() 795: * @see #getUseSeriesOffset() 796: * 797: * @since 1.0.7 798: */ 799: public void setItemMargin(double margin) { 800: if (margin < 0.0 || margin >= 1.0) { 801: throw new IllegalArgumentException("Requires 0.0 <= margin < 1.0."); 802: } 803: this.itemMargin = margin; 804: fireChangeEvent(); 805: } 806: 807: /** 808: * Returns a legend item for a series. 809: * 810: * @param datasetIndex the dataset index (zero-based). 811: * @param series the series index (zero-based). 812: * 813: * @return The legend item. 814: */ 815: public LegendItem getLegendItem(int datasetIndex, int series) { 816: 817: CategoryPlot cp = getPlot(); 818: if (cp == null) { 819: return null; 820: } 821: 822: if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) { 823: CategoryDataset dataset = cp.getDataset(datasetIndex); 824: String label = getLegendItemLabelGenerator().generateLabel( 825: dataset, series); 826: String description = label; 827: String toolTipText = null; 828: if (getLegendItemToolTipGenerator() != null) { 829: toolTipText = getLegendItemToolTipGenerator().generateLabel( 830: dataset, series); 831: } 832: String urlText = null; 833: if (getLegendItemURLGenerator() != null) { 834: urlText = getLegendItemURLGenerator().generateLabel( 835: dataset, series); 836: } 837: Shape shape = lookupSeriesShape(series); 838: Paint paint = lookupSeriesPaint(series); 839: Paint fillPaint = (this.useFillPaint 840: ? getItemFillPaint(series, 0) : paint); 841: boolean shapeOutlineVisible = this.drawOutlines; 842: Paint outlinePaint = (this.useOutlinePaint 843: ? getItemOutlinePaint(series, 0) : paint); 844: Stroke outlineStroke = lookupSeriesOutlineStroke(series); 845: boolean lineVisible = getItemLineVisible(series, 0); 846: boolean shapeVisible = getItemShapeVisible(series, 0); 847: LegendItem result = new LegendItem(label, description, toolTipText, 848: urlText, shapeVisible, shape, getItemShapeFilled(series, 0), 849: fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke, 850: lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0), 851: getItemStroke(series, 0), getItemPaint(series, 0)); 852: result.setDataset(dataset); 853: result.setDatasetIndex(datasetIndex); 854: result.setSeriesKey(dataset.getRowKey(series)); 855: result.setSeriesIndex(series); 856: return result; 857: } 858: return null; 859: 860: } 861: 862: /** 863: * This renderer uses two passes to draw the data. 864: * 865: * @return The pass count (<code>2</code> for this renderer). 866: */ 867: public int getPassCount() { 868: return 2; 869: } 870: 871: /** 872: * Draw a single data item. 873: * 874: * @param g2 the graphics device. 875: * @param state the renderer state. 876: * @param dataArea the area in which the data is drawn. 877: * @param plot the plot. 878: * @param domainAxis the domain axis. 879: * @param rangeAxis the range axis. 880: * @param dataset the dataset. 881: * @param row the row index (zero-based). 882: * @param column the column index (zero-based). 883: * @param pass the pass index. 884: */ 885: public void drawItem(Graphics2D g2, CategoryItemRendererState state, 886: Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 887: ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, 888: int pass) { 889: 890: // do nothing if item is not visible 891: if (!getItemVisible(row, column)) { 892: return; 893: } 894: 895: // do nothing if both the line and shape are not visible 896: if (!getItemLineVisible(row, column) 897: && !getItemShapeVisible(row, column)) { 898: return; 899: } 900: 901: // nothing is drawn for null... 902: Number v = dataset.getValue(row, column); 903: if (v == null) { 904: return; 905: } 906: 907: PlotOrientation orientation = plot.getOrientation(); 908: 909: // current data point... 910: double x1; 911: if (this.useSeriesOffset) { 912: x1 = domainAxis.getCategorySeriesMiddle(dataset.getColumnKey( 913: column), dataset.getRowKey(row), dataset, this.itemMargin, 914: dataArea, plot.getDomainAxisEdge()); 915: } 916: else { 917: x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 918: dataArea, plot.getDomainAxisEdge()); 919: } 920: double value = v.doubleValue(); 921: double y1 = rangeAxis.valueToJava2D(value, dataArea, 922: plot.getRangeAxisEdge()); 923: 924: if (pass == 0 && getItemLineVisible(row, column)) { 925: if (column != 0) { 926: Number previousValue = dataset.getValue(row, column - 1); 927: if (previousValue != null) { 928: // previous data point... 929: double previous = previousValue.doubleValue(); 930: double x0; 931: if (this.useSeriesOffset) { 932: x0 = domainAxis.getCategorySeriesMiddle( 933: dataset.getColumnKey(column - 1), 934: dataset.getRowKey(row), dataset, 935: this.itemMargin, dataArea, 936: plot.getDomainAxisEdge()); 937: } 938: else { 939: x0 = domainAxis.getCategoryMiddle(column - 1, 940: getColumnCount(), dataArea, 941: plot.getDomainAxisEdge()); 942: } 943: double y0 = rangeAxis.valueToJava2D(previous, dataArea, 944: plot.getRangeAxisEdge()); 945: 946: Line2D line = null; 947: if (orientation == PlotOrientation.HORIZONTAL) { 948: line = new Line2D.Double(y0, x0, y1, x1); 949: } 950: else if (orientation == PlotOrientation.VERTICAL) { 951: line = new Line2D.Double(x0, y0, x1, y1); 952: } 953: g2.setPaint(getItemPaint(row, column)); 954: g2.setStroke(getItemStroke(row, column)); 955: g2.draw(line); 956: } 957: } 958: } 959: 960: if (pass == 1) { 961: Shape shape = getItemShape(row, column); 962: if (orientation == PlotOrientation.HORIZONTAL) { 963: shape = ShapeUtilities.createTranslatedShape(shape, y1, x1); 964: } 965: else if (orientation == PlotOrientation.VERTICAL) { 966: shape = ShapeUtilities.createTranslatedShape(shape, x1, y1); 967: } 968: 969: if (getItemShapeVisible(row, column)) { 970: if (getItemShapeFilled(row, column)) { 971: if (this.useFillPaint) { 972: g2.setPaint(getItemFillPaint(row, column)); 973: } 974: else { 975: g2.setPaint(getItemPaint(row, column)); 976: } 977: g2.fill(shape); 978: } 979: if (this.drawOutlines) { 980: if (this.useOutlinePaint) { 981: g2.setPaint(getItemOutlinePaint(row, column)); 982: } 983: else { 984: g2.setPaint(getItemPaint(row, column)); 985: } 986: g2.setStroke(getItemOutlineStroke(row, column)); 987: g2.draw(shape); 988: } 989: } 990: 991: // draw the item label if there is one... 992: if (isItemLabelVisible(row, column)) { 993: if (orientation == PlotOrientation.HORIZONTAL) { 994: drawItemLabel(g2, orientation, dataset, row, column, y1, 995: x1, (value < 0.0)); 996: } 997: else if (orientation == PlotOrientation.VERTICAL) { 998: drawItemLabel(g2, orientation, dataset, row, column, x1, 999: y1, (value < 0.0)); 1000: } 1001: } 1002: 1003: // add an item entity, if this information is being collected 1004: EntityCollection entities = state.getEntityCollection(); 1005: if (entities != null) { 1006: addItemEntity(entities, dataset, row, column, shape); 1007: } 1008: } 1009: 1010: } 1011: 1012: /** 1013: * Tests this renderer for equality with an arbitrary object. 1014: * 1015: * @param obj the object (<code>null</code> permitted). 1016: * 1017: * @return A boolean. 1018: */ 1019: public boolean equals(Object obj) { 1020: 1021: if (obj == this) { 1022: return true; 1023: } 1024: if (!(obj instanceof LineAndShapeRenderer)) { 1025: return false; 1026: } 1027: 1028: LineAndShapeRenderer that = (LineAndShapeRenderer) obj; 1029: if (this.baseLinesVisible != that.baseLinesVisible) { 1030: return false; 1031: } 1032: if (!ObjectUtilities.equal(this.seriesLinesVisible, 1033: that.seriesLinesVisible)) { 1034: return false; 1035: } 1036: if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) { 1037: return false; 1038: } 1039: if (this.baseShapesVisible != that.baseShapesVisible) { 1040: return false; 1041: } 1042: if (!ObjectUtilities.equal(this.seriesShapesVisible, 1043: that.seriesShapesVisible)) { 1044: return false; 1045: } 1046: if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) { 1047: return false; 1048: } 1049: if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1050: return false; 1051: } 1052: if (!ObjectUtilities.equal(this.seriesShapesFilled, 1053: that.seriesShapesFilled)) { 1054: return false; 1055: } 1056: if (this.baseShapesFilled != that.baseShapesFilled) { 1057: return false; 1058: } 1059: if (this.useOutlinePaint != that.useOutlinePaint) { 1060: return false; 1061: } 1062: if (this.useSeriesOffset != that.useSeriesOffset) { 1063: return false; 1064: } 1065: if (this.itemMargin != that.itemMargin) { 1066: return false; 1067: } 1068: return super.equals(obj); 1069: } 1070: 1071: /** 1072: * Returns an independent copy of the renderer. 1073: * 1074: * @return A clone. 1075: * 1076: * @throws CloneNotSupportedException should not happen. 1077: */ 1078: public Object clone() throws CloneNotSupportedException { 1079: LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone(); 1080: clone.seriesLinesVisible 1081: = (BooleanList) this.seriesLinesVisible.clone(); 1082: clone.seriesShapesVisible 1083: = (BooleanList) this.seriesShapesVisible.clone(); 1084: clone.seriesShapesFilled 1085: = (BooleanList) this.seriesShapesFilled.clone(); 1086: return clone; 1087: } 1088: 1089: }