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