001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------------- 028 * LineAndShapeRenderer.java 029 * ------------------------- 030 * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Mark Watson (www.markwatson.com); 034 * Jeremy Bowman; 035 * Richard Atkinson; 036 * Christian W. Zuckschwerdt; 037 * 038 * Changes 039 * ------- 040 * 23-Oct-2001 : Version 1 (DG); 041 * 15-Nov-2001 : Modified to allow for null data values (DG); 042 * 16-Jan-2002 : Renamed HorizontalCategoryItemRenderer.java 043 * --> CategoryItemRenderer.java (DG); 044 * 05-Feb-2002 : Changed return type of the drawCategoryItem method from void 045 * to Shape, as part of the tooltips implementation (DG); 046 * 11-May-2002 : Support for value label drawing (JB); 047 * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG); 048 * 25-Jun-2002 : Removed redundant import (DG); 049 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 050 * for HTML image maps (RA); 051 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG); 052 * 11-Oct-2002 : Added new constructor to incorporate tool tip and URL 053 * generators (DG); 054 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 055 * CategoryToolTipGenerator interface (DG); 056 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG); 057 * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis 058 * for category spacing (DG); 059 * 17-Jan-2003 : Moved plot classes to a separate package (DG); 060 * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem() 061 * method (DG); 062 * 12-May-2003 : Modified to take into account the plot orientation (DG); 063 * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG); 064 * 30-Jul-2003 : Modified entity constructor (CZ); 065 * 22-Sep-2003 : Fixed cloning (DG); 066 * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 067 * override easier (DG); 068 * 16-Jun-2004 : Fixed bug (id=972454) with label positioning on horizontal 069 * charts (DG); 070 * 15-Oct-2004 : Updated equals() method (DG); 071 * 05-Nov-2004 : Modified drawItem() signature (DG); 072 * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG); 073 * 27-Jan-2005 : Changed attribute names, modified constructor and removed 074 * constants (DG); 075 * 01-Feb-2005 : Removed unnecessary constants (DG); 076 * 15-Mar-2005 : Fixed bug 1163897, concerning outlines for shapes (DG); 077 * 13-Apr-2005 : Check flags that control series visibility (DG); 078 * 20-Apr-2005 : Use generators for legend labels, tooltips and URLs (DG); 079 * 09-Jun-2005 : Use addItemEntity() method (DG); 080 * ------------- JFREECHART 1.0.x --------------------------------------------- 081 * 25-May-2006 : Added check to drawItem() to detect when both the line and 082 * the shape are not visible (DG); 083 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 084 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG); 085 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 086 * 24-Sep-2007 : Deprecated redundant fields/methods (DG); 087 * 27-Sep-2007 : Added option to offset series x-position within category (DG); 088 * 089 */ 090 091 package org.jfree.chart.renderer.category; 092 093 import java.awt.Graphics2D; 094 import java.awt.Paint; 095 import java.awt.Shape; 096 import java.awt.Stroke; 097 import java.awt.geom.Line2D; 098 import java.awt.geom.Rectangle2D; 099 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 }