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