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