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 * XYLineAndShapeRenderer.java 029 * --------------------------- 030 * (C) Copyright 2004-2014, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes: 036 * -------- 037 * 27-Jan-2004 : Version 1 (DG); 038 * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste 039 * overriding easier (DG); 040 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 041 * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG); 042 * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path 043 * (necessary when using a dashed stroke with many data 044 * items) (DG); 045 * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG); 046 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 047 * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG); 048 * 28-Jan-2005 : Added new constructor (DG); 049 * 09-Mar-2005 : Added fillPaint settings (DG); 050 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); 051 * 22-Jul-2005 : Renamed defaultLinesVisible --> baseLinesVisible, 052 * defaultShapesVisible --> baseShapesVisible and 053 * defaultShapesFilled --> baseShapesFilled (DG); 054 * 29-Jul-2005 : Added code to draw item labels (DG); 055 * ------------- JFREECHART 1.0.x --------------------------------------------- 056 * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG); 057 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG); 058 * 21-Feb-2007 : Fixed bugs in clone() and equals() (DG); 059 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG); 060 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 061 * 08-Jun-2007 : Fix for bug 1731912 where entities are created even for data 062 * items that are not displayed (DG); 063 * 26-Oct-2007 : Deprecated override attributes (DG); 064 * 02-Jun-2008 : Fixed tooltips at lower edges of data area (DG); 065 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG); 066 * 19-Sep-2008 : Fixed bug with drawSeriesLineAsPath - patch by Greg Darke (DG); 067 * 18-May-2009 : Clip lines in drawPrimaryLine() (DG); 068 * 05-Jul-2012 : Removed JDK 1.3.1 code (DG); 069 * 02-Jul-2013 : Use ParamChecks (DG); 070 * 071 */ 072 073package org.jfree.chart.renderer.xy; 074 075import java.awt.Graphics2D; 076import java.awt.Paint; 077import java.awt.Shape; 078import java.awt.Stroke; 079import java.awt.geom.GeneralPath; 080import java.awt.geom.Line2D; 081import java.awt.geom.Rectangle2D; 082import java.io.IOException; 083import java.io.ObjectInputStream; 084import java.io.ObjectOutputStream; 085import java.io.Serializable; 086 087import org.jfree.chart.LegendItem; 088import org.jfree.chart.axis.ValueAxis; 089import org.jfree.chart.entity.EntityCollection; 090import org.jfree.chart.event.RendererChangeEvent; 091import org.jfree.chart.plot.CrosshairState; 092import org.jfree.chart.plot.PlotOrientation; 093import org.jfree.chart.plot.PlotRenderingInfo; 094import org.jfree.chart.plot.XYPlot; 095import org.jfree.chart.util.LineUtilities; 096import org.jfree.chart.util.ParamChecks; 097import org.jfree.data.xy.XYDataset; 098import org.jfree.io.SerialUtilities; 099import org.jfree.ui.RectangleEdge; 100import org.jfree.util.BooleanList; 101import org.jfree.util.ObjectUtilities; 102import org.jfree.util.PublicCloneable; 103import org.jfree.util.ShapeUtilities; 104 105/** 106 * A renderer that connects data points with lines and/or draws shapes at each 107 * data point. This renderer is designed for use with the {@link XYPlot} 108 * class. The example shown here is generated by 109 * the <code>XYLineAndShapeRendererDemo2.java</code> program included in the 110 * JFreeChart demo collection: 111 * <br><br> 112 * <img src="../../../../../images/XYLineAndShapeRendererSample.png" 113 * alt="XYLineAndShapeRendererSample.png"> 114 * 115 */ 116public class XYLineAndShapeRenderer extends AbstractXYItemRenderer 117 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 118 119 /** For serialization. */ 120 private static final long serialVersionUID = -7435246895986425885L; 121 122 /** 123 * A flag that controls whether or not lines are visible for ALL series. 124 * 125 * @deprecated As of 1.0.7. 126 */ 127 private Boolean linesVisible; 128 129 /** 130 * A table of flags that control (per series) whether or not lines are 131 * visible. 132 */ 133 private BooleanList seriesLinesVisible; 134 135 /** The default value returned by the getLinesVisible() method. */ 136 private boolean baseLinesVisible; 137 138 /** The shape that is used to represent a line in the legend. */ 139 private transient Shape legendLine; 140 141 /** 142 * A flag that controls whether or not shapes are visible for ALL series. 143 * 144 * @deprecated As of 1.0.7. 145 */ 146 private Boolean shapesVisible; 147 148 /** 149 * A table of flags that control (per series) whether or not shapes are 150 * visible. 151 */ 152 private BooleanList seriesShapesVisible; 153 154 /** The default value returned by the getShapeVisible() method. */ 155 private boolean baseShapesVisible; 156 157 /** 158 * A flag that controls whether or not shapes are filled for ALL series. 159 * 160 * @deprecated As of 1.0.7. 161 */ 162 private Boolean shapesFilled; 163 164 /** 165 * A table of flags that control (per series) whether or not shapes are 166 * filled. 167 */ 168 private BooleanList seriesShapesFilled; 169 170 /** The default value returned by the getShapeFilled() method. */ 171 private boolean baseShapesFilled; 172 173 /** A flag that controls whether outlines are drawn for shapes. */ 174 private boolean drawOutlines; 175 176 /** 177 * A flag that controls whether the fill paint is used for filling 178 * shapes. 179 */ 180 private boolean useFillPaint; 181 182 /** 183 * A flag that controls whether the outline paint is used for drawing shape 184 * outlines. 185 */ 186 private boolean useOutlinePaint; 187 188 /** 189 * A flag that controls whether or not each series is drawn as a single 190 * path. 191 */ 192 private boolean drawSeriesLineAsPath; 193 194 /** 195 * Creates a new renderer with both lines and shapes visible. 196 */ 197 public XYLineAndShapeRenderer() { 198 this(true, true); 199 } 200 201 /** 202 * Creates a new renderer. 203 * 204 * @param lines lines visible? 205 * @param shapes shapes visible? 206 */ 207 public XYLineAndShapeRenderer(boolean lines, boolean shapes) { 208 this.linesVisible = null; 209 this.seriesLinesVisible = new BooleanList(); 210 this.baseLinesVisible = lines; 211 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 212 213 this.shapesVisible = null; 214 this.seriesShapesVisible = new BooleanList(); 215 this.baseShapesVisible = shapes; 216 217 this.shapesFilled = null; 218 this.useFillPaint = false; // use item paint for fills by default 219 this.seriesShapesFilled = new BooleanList(); 220 this.baseShapesFilled = true; 221 222 this.drawOutlines = true; 223 this.useOutlinePaint = false; // use item paint for outlines by 224 // default, not outline paint 225 226 this.drawSeriesLineAsPath = false; 227 } 228 229 /** 230 * Returns a flag that controls whether or not each series is drawn as a 231 * single path. 232 * 233 * @return A boolean. 234 * 235 * @see #setDrawSeriesLineAsPath(boolean) 236 */ 237 public boolean getDrawSeriesLineAsPath() { 238 return this.drawSeriesLineAsPath; 239 } 240 241 /** 242 * Sets the flag that controls whether or not each series is drawn as a 243 * single path and sends a {@link RendererChangeEvent} to all registered 244 * listeners. 245 * 246 * @param flag the flag. 247 * 248 * @see #getDrawSeriesLineAsPath() 249 */ 250 public void setDrawSeriesLineAsPath(boolean flag) { 251 if (this.drawSeriesLineAsPath != flag) { 252 this.drawSeriesLineAsPath = flag; 253 fireChangeEvent(); 254 } 255 } 256 257 /** 258 * Returns the number of passes through the data that the renderer requires 259 * in order to draw the chart. Most charts will require a single pass, but 260 * some require two passes. 261 * 262 * @return The pass count. 263 */ 264 @Override 265 public int getPassCount() { 266 return 2; 267 } 268 269 // LINES VISIBLE 270 271 /** 272 * Returns the flag used to control whether or not the shape for an item is 273 * visible. 274 * 275 * @param series the series index (zero-based). 276 * @param item the item index (zero-based). 277 * 278 * @return A boolean. 279 */ 280 public boolean getItemLineVisible(int series, int item) { 281 Boolean flag = this.linesVisible; 282 if (flag == null) { 283 flag = getSeriesLinesVisible(series); 284 } 285 if (flag != null) { 286 return flag.booleanValue(); 287 } 288 else { 289 return this.baseLinesVisible; 290 } 291 } 292 293 /** 294 * Returns a flag that controls whether or not lines are drawn for ALL 295 * series. If this flag is <code>null</code>, then the "per series" 296 * settings will apply. 297 * 298 * @return A flag (possibly <code>null</code>). 299 * 300 * @see #setLinesVisible(Boolean) 301 * 302 * @deprecated As of 1.0.7, use the per-series and base level settings. 303 */ 304 public Boolean getLinesVisible() { 305 return this.linesVisible; 306 } 307 308 /** 309 * Sets a flag that controls whether or not lines are drawn between the 310 * items in ALL series, and sends a {@link RendererChangeEvent} to all 311 * registered listeners. You need to set this to <code>null</code> if you 312 * want the "per series" settings to apply. 313 * 314 * @param visible the flag (<code>null</code> permitted). 315 * 316 * @see #getLinesVisible() 317 * 318 * @deprecated As of 1.0.7, use the per-series and base level settings. 319 */ 320 public void setLinesVisible(Boolean visible) { 321 this.linesVisible = visible; 322 fireChangeEvent(); 323 } 324 325 /** 326 * Sets a flag that controls whether or not lines are drawn between the 327 * items in ALL series, and sends a {@link RendererChangeEvent} to all 328 * registered listeners. 329 * 330 * @param visible the flag. 331 * 332 * @see #getLinesVisible() 333 * 334 * @deprecated As of 1.0.7, use the per-series and base level settings. 335 */ 336 public void setLinesVisible(boolean visible) { 337 setLinesVisible(Boolean.valueOf(visible)); 338 } 339 340 /** 341 * Returns the flag used to control whether or not the lines for a series 342 * are visible. 343 * 344 * @param series the series index (zero-based). 345 * 346 * @return The flag (possibly <code>null</code>). 347 * 348 * @see #setSeriesLinesVisible(int, Boolean) 349 */ 350 public Boolean getSeriesLinesVisible(int series) { 351 return this.seriesLinesVisible.getBoolean(series); 352 } 353 354 /** 355 * Sets the 'lines visible' flag for a series and sends a 356 * {@link RendererChangeEvent} to all registered listeners. 357 * 358 * @param series the series index (zero-based). 359 * @param flag the flag (<code>null</code> permitted). 360 * 361 * @see #getSeriesLinesVisible(int) 362 */ 363 public void setSeriesLinesVisible(int series, Boolean flag) { 364 this.seriesLinesVisible.setBoolean(series, flag); 365 fireChangeEvent(); 366 } 367 368 /** 369 * Sets the 'lines visible' flag for a series and sends a 370 * {@link RendererChangeEvent} to all registered listeners. 371 * 372 * @param series the series index (zero-based). 373 * @param visible the flag. 374 * 375 * @see #getSeriesLinesVisible(int) 376 */ 377 public void setSeriesLinesVisible(int series, boolean visible) { 378 setSeriesLinesVisible(series, Boolean.valueOf(visible)); 379 } 380 381 /** 382 * Returns the base 'lines visible' attribute. 383 * 384 * @return The base flag. 385 * 386 * @see #setBaseLinesVisible(boolean) 387 */ 388 public boolean getBaseLinesVisible() { 389 return this.baseLinesVisible; 390 } 391 392 /** 393 * Sets the base 'lines visible' flag and sends a 394 * {@link RendererChangeEvent} to all registered listeners. 395 * 396 * @param flag the flag. 397 * 398 * @see #getBaseLinesVisible() 399 */ 400 public void setBaseLinesVisible(boolean flag) { 401 this.baseLinesVisible = flag; 402 fireChangeEvent(); 403 } 404 405 /** 406 * Returns the shape used to represent a line in the legend. 407 * 408 * @return The legend line (never <code>null</code>). 409 * 410 * @see #setLegendLine(Shape) 411 */ 412 public Shape getLegendLine() { 413 return this.legendLine; 414 } 415 416 /** 417 * Sets the shape used as a line in each legend item and sends a 418 * {@link RendererChangeEvent} to all registered listeners. 419 * 420 * @param line the line (<code>null</code> not permitted). 421 * 422 * @see #getLegendLine() 423 */ 424 public void setLegendLine(Shape line) { 425 ParamChecks.nullNotPermitted(line, "line"); 426 this.legendLine = line; 427 fireChangeEvent(); 428 } 429 430 // SHAPES VISIBLE 431 432 /** 433 * Returns the flag used to control whether or not the shape for an item is 434 * visible. 435 * <p> 436 * The default implementation passes control to the 437 * <code>getSeriesShapesVisible</code> method. You can override this method 438 * if you require different behaviour. 439 * 440 * @param series the series index (zero-based). 441 * @param item the item index (zero-based). 442 * 443 * @return A boolean. 444 */ 445 public boolean getItemShapeVisible(int series, int item) { 446 Boolean flag = this.shapesVisible; 447 if (flag == null) { 448 flag = getSeriesShapesVisible(series); 449 } 450 if (flag != null) { 451 return flag.booleanValue(); 452 } 453 else { 454 return this.baseShapesVisible; 455 } 456 } 457 458 /** 459 * Returns the flag that controls whether the shapes are visible for the 460 * items in ALL series. 461 * 462 * @return The flag (possibly <code>null</code>). 463 * 464 * @see #setShapesVisible(Boolean) 465 * 466 * @deprecated As of 1.0.7, use the per-series and base level settings. 467 */ 468 public Boolean getShapesVisible() { 469 return this.shapesVisible; 470 } 471 472 /** 473 * Sets the 'shapes visible' for ALL series and sends a 474 * {@link RendererChangeEvent} to all registered listeners. 475 * 476 * @param visible the flag (<code>null</code> permitted). 477 * 478 * @see #getShapesVisible() 479 * 480 * @deprecated As of 1.0.7, use the per-series and base level settings. 481 */ 482 public void setShapesVisible(Boolean visible) { 483 this.shapesVisible = visible; 484 fireChangeEvent(); 485 } 486 487 /** 488 * Sets the 'shapes visible' for ALL series and sends a 489 * {@link RendererChangeEvent} to all registered listeners. 490 * 491 * @param visible the flag. 492 * 493 * @see #getShapesVisible() 494 * 495 * @deprecated As of 1.0.7, use the per-series and base level settings. 496 */ 497 public void setShapesVisible(boolean visible) { 498 setShapesVisible(Boolean.valueOf(visible)); 499 } 500 501 /** 502 * Returns the flag used to control whether or not the shapes for a series 503 * are visible. 504 * 505 * @param series the series index (zero-based). 506 * 507 * @return A boolean. 508 * 509 * @see #setSeriesShapesVisible(int, Boolean) 510 */ 511 public Boolean getSeriesShapesVisible(int series) { 512 return this.seriesShapesVisible.getBoolean(series); 513 } 514 515 /** 516 * Sets the 'shapes visible' flag for a series and sends a 517 * {@link RendererChangeEvent} to all registered listeners. 518 * 519 * @param series the series index (zero-based). 520 * @param visible the flag. 521 * 522 * @see #getSeriesShapesVisible(int) 523 */ 524 public void setSeriesShapesVisible(int series, boolean visible) { 525 setSeriesShapesVisible(series, Boolean.valueOf(visible)); 526 } 527 528 /** 529 * Sets the 'shapes visible' flag for a series and sends a 530 * {@link RendererChangeEvent} to all registered listeners. 531 * 532 * @param series the series index (zero-based). 533 * @param flag the flag. 534 * 535 * @see #getSeriesShapesVisible(int) 536 */ 537 public void setSeriesShapesVisible(int series, Boolean flag) { 538 this.seriesShapesVisible.setBoolean(series, flag); 539 fireChangeEvent(); 540 } 541 542 /** 543 * Returns the base 'shape visible' attribute. 544 * 545 * @return The base flag. 546 * 547 * @see #setBaseShapesVisible(boolean) 548 */ 549 public boolean getBaseShapesVisible() { 550 return this.baseShapesVisible; 551 } 552 553 /** 554 * Sets the base 'shapes visible' flag and sends a 555 * {@link RendererChangeEvent} to all registered listeners. 556 * 557 * @param flag the flag. 558 * 559 * @see #getBaseShapesVisible() 560 */ 561 public void setBaseShapesVisible(boolean flag) { 562 this.baseShapesVisible = flag; 563 fireChangeEvent(); 564 } 565 566 // SHAPES FILLED 567 568 /** 569 * Returns the flag used to control whether or not the shape for an item 570 * is filled. 571 * <p> 572 * 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 Boolean flag = this.shapesFilled; 583 if (flag == null) { 584 flag = getSeriesShapesFilled(series); 585 } 586 if (flag != null) { 587 return flag.booleanValue(); 588 } 589 else { 590 return this.baseShapesFilled; 591 } 592 } 593 594 /** 595 * Sets the 'shapes filled' for ALL series and sends a 596 * {@link RendererChangeEvent} to all registered listeners. 597 * 598 * @param filled the flag. 599 * 600 * @deprecated As of 1.0.7, use the per-series and base level settings. 601 */ 602 public void setShapesFilled(boolean filled) { 603 setShapesFilled(Boolean.valueOf(filled)); 604 } 605 606 /** 607 * Sets the 'shapes filled' for ALL series and sends a 608 * {@link RendererChangeEvent} to all registered listeners. 609 * 610 * @param filled the flag (<code>null</code> permitted). 611 * 612 * @deprecated As of 1.0.7, use the per-series and base level settings. 613 */ 614 public void setShapesFilled(Boolean filled) { 615 this.shapesFilled = filled; 616 fireChangeEvent(); 617 } 618 619 /** 620 * Returns the flag used to control whether or not the shapes for a series 621 * are filled. 622 * 623 * @param series the series index (zero-based). 624 * 625 * @return A boolean. 626 * 627 * @see #setSeriesShapesFilled(int, Boolean) 628 */ 629 public Boolean getSeriesShapesFilled(int series) { 630 return this.seriesShapesFilled.getBoolean(series); 631 } 632 633 /** 634 * Sets the 'shapes filled' flag for a series and sends a 635 * {@link RendererChangeEvent} to all registered listeners. 636 * 637 * @param series the series index (zero-based). 638 * @param flag the flag. 639 * 640 * @see #getSeriesShapesFilled(int) 641 */ 642 public void setSeriesShapesFilled(int series, boolean flag) { 643 setSeriesShapesFilled(series, Boolean.valueOf(flag)); 644 } 645 646 /** 647 * Sets the 'shapes filled' flag for a series and sends a 648 * {@link RendererChangeEvent} to all registered listeners. 649 * 650 * @param series the series index (zero-based). 651 * @param flag the flag. 652 * 653 * @see #getSeriesShapesFilled(int) 654 */ 655 public void setSeriesShapesFilled(int series, Boolean flag) { 656 this.seriesShapesFilled.setBoolean(series, flag); 657 fireChangeEvent(); 658 } 659 660 /** 661 * Returns the base 'shape filled' attribute. 662 * 663 * @return The base flag. 664 * 665 * @see #setBaseShapesFilled(boolean) 666 */ 667 public boolean getBaseShapesFilled() { 668 return this.baseShapesFilled; 669 } 670 671 /** 672 * Sets the base 'shapes filled' flag and sends a 673 * {@link RendererChangeEvent} to all registered listeners. 674 * 675 * @param flag the flag. 676 * 677 * @see #getBaseShapesFilled() 678 */ 679 public void setBaseShapesFilled(boolean flag) { 680 this.baseShapesFilled = flag; 681 fireChangeEvent(); 682 } 683 684 /** 685 * Returns <code>true</code> if outlines should be drawn for shapes, and 686 * <code>false</code> otherwise. 687 * 688 * @return A boolean. 689 * 690 * @see #setDrawOutlines(boolean) 691 */ 692 public boolean getDrawOutlines() { 693 return this.drawOutlines; 694 } 695 696 /** 697 * Sets the flag that controls whether outlines are drawn for 698 * shapes, and sends a {@link RendererChangeEvent} to all registered 699 * listeners. 700 * <P> 701 * In some cases, shapes look better if they do NOT have an outline, but 702 * this flag allows you to set your own preference. 703 * 704 * @param flag the flag. 705 * 706 * @see #getDrawOutlines() 707 */ 708 public void setDrawOutlines(boolean flag) { 709 this.drawOutlines = flag; 710 fireChangeEvent(); 711 } 712 713 /** 714 * Returns <code>true</code> if the renderer should use the fill paint 715 * setting to fill shapes, and <code>false</code> if it should just 716 * use the regular paint. 717 * <p> 718 * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the 719 * effect of this flag. 720 * 721 * @return A boolean. 722 * 723 * @see #setUseFillPaint(boolean) 724 * @see #getUseOutlinePaint() 725 */ 726 public boolean getUseFillPaint() { 727 return this.useFillPaint; 728 } 729 730 /** 731 * Sets the flag that controls whether the fill paint is used to fill 732 * shapes, and sends a {@link RendererChangeEvent} to all 733 * registered listeners. 734 * 735 * @param flag the flag. 736 * 737 * @see #getUseFillPaint() 738 */ 739 public void setUseFillPaint(boolean flag) { 740 this.useFillPaint = flag; 741 fireChangeEvent(); 742 } 743 744 /** 745 * Returns <code>true</code> if the renderer should use the outline paint 746 * setting to draw shape outlines, and <code>false</code> if it should just 747 * use the regular paint. 748 * 749 * @return A boolean. 750 * 751 * @see #setUseOutlinePaint(boolean) 752 * @see #getUseFillPaint() 753 */ 754 public boolean getUseOutlinePaint() { 755 return this.useOutlinePaint; 756 } 757 758 /** 759 * Sets the flag that controls whether the outline paint is used to draw 760 * shape outlines, and sends a {@link RendererChangeEvent} to all 761 * registered listeners. 762 * <p> 763 * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the 764 * effect of this flag. 765 * 766 * @param flag the flag. 767 * 768 * @see #getUseOutlinePaint() 769 */ 770 public void setUseOutlinePaint(boolean flag) { 771 this.useOutlinePaint = flag; 772 fireChangeEvent(); 773 } 774 775 /** 776 * Records the state for the renderer. This is used to preserve state 777 * information between calls to the drawItem() method for a single chart 778 * drawing. 779 */ 780 public static class State extends XYItemRendererState { 781 782 /** The path for the current series. */ 783 public GeneralPath seriesPath; 784 785 /** 786 * A flag that indicates if the last (x, y) point was 'good' 787 * (non-null). 788 */ 789 private boolean lastPointGood; 790 791 /** 792 * Creates a new state instance. 793 * 794 * @param info the plot rendering info. 795 */ 796 public State(PlotRenderingInfo info) { 797 super(info); 798 this.seriesPath = new GeneralPath(); 799 } 800 801 /** 802 * Returns a flag that indicates if the last point drawn (in the 803 * current series) was 'good' (non-null). 804 * 805 * @return A boolean. 806 */ 807 public boolean isLastPointGood() { 808 return this.lastPointGood; 809 } 810 811 /** 812 * Sets a flag that indicates if the last point drawn (in the current 813 * series) was 'good' (non-null). 814 * 815 * @param good the flag. 816 */ 817 public void setLastPointGood(boolean good) { 818 this.lastPointGood = good; 819 } 820 821 /** 822 * This method is called by the {@link XYPlot} at the start of each 823 * series pass. We reset the state for the current series. 824 * 825 * @param dataset the dataset. 826 * @param series the series index. 827 * @param firstItem the first item index for this pass. 828 * @param lastItem the last item index for this pass. 829 * @param pass the current pass index. 830 * @param passCount the number of passes. 831 */ 832 @Override 833 public void startSeriesPass(XYDataset dataset, int series, 834 int firstItem, int lastItem, int pass, int passCount) { 835 this.seriesPath.reset(); 836 this.lastPointGood = false; 837 super.startSeriesPass(dataset, series, firstItem, lastItem, pass, 838 passCount); 839 } 840 841 } 842 843 /** 844 * Initialises the renderer. 845 * <P> 846 * This method will be called before the first item is rendered, giving the 847 * renderer an opportunity to initialise any state information it wants to 848 * maintain. The renderer can do nothing if it chooses. 849 * 850 * @param g2 the graphics device. 851 * @param dataArea the area inside the axes. 852 * @param plot the plot. 853 * @param data the data. 854 * @param info an optional info collection object to return data back to 855 * the caller. 856 * 857 * @return The renderer state. 858 */ 859 @Override 860 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 861 XYPlot plot, XYDataset data, PlotRenderingInfo info) { 862 return new State(info); 863 } 864 865 /** 866 * Draws the visual representation of a single data item. 867 * 868 * @param g2 the graphics device. 869 * @param state the renderer state. 870 * @param dataArea the area within which the data is being drawn. 871 * @param info collects information about the drawing. 872 * @param plot the plot (can be used to obtain standard color 873 * information etc). 874 * @param domainAxis the domain axis. 875 * @param rangeAxis the range axis. 876 * @param dataset the dataset. 877 * @param series the series index (zero-based). 878 * @param item the item index (zero-based). 879 * @param crosshairState crosshair information for the plot 880 * (<code>null</code> permitted). 881 * @param pass the pass index. 882 */ 883 @Override 884 public void drawItem(Graphics2D g2, XYItemRendererState state, 885 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 886 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 887 int series, int item, CrosshairState crosshairState, int pass) { 888 889 // do nothing if item is not visible 890 if (!getItemVisible(series, item)) { 891 return; 892 } 893 894 // first pass draws the background (lines, for instance) 895 if (isLinePass(pass)) { 896 if (getItemLineVisible(series, item)) { 897 if (this.drawSeriesLineAsPath) { 898 drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 899 series, item, domainAxis, rangeAxis, dataArea); 900 } 901 else { 902 drawPrimaryLine(state, g2, plot, dataset, pass, series, 903 item, domainAxis, rangeAxis, dataArea); 904 } 905 } 906 } 907 // second pass adds shapes where the items are .. 908 else if (isItemPass(pass)) { 909 910 // setup for collecting optional entity info... 911 EntityCollection entities = null; 912 if (info != null && info.getOwner() != null) { 913 entities = info.getOwner().getEntityCollection(); 914 } 915 916 drawSecondaryPass(g2, plot, dataset, pass, series, item, 917 domainAxis, dataArea, rangeAxis, crosshairState, entities); 918 } 919 } 920 921 /** 922 * Returns <code>true</code> if the specified pass is the one for drawing 923 * lines. 924 * 925 * @param pass the pass. 926 * 927 * @return A boolean. 928 */ 929 protected boolean isLinePass(int pass) { 930 return pass == 0; 931 } 932 933 /** 934 * Returns <code>true</code> if the specified pass is the one for drawing 935 * items. 936 * 937 * @param pass the pass. 938 * 939 * @return A boolean. 940 */ 941 protected boolean isItemPass(int pass) { 942 return pass == 1; 943 } 944 945 /** 946 * Draws the item (first pass). This method draws the lines 947 * connecting the items. 948 * 949 * @param g2 the graphics device. 950 * @param state the renderer state. 951 * @param dataArea the area within which the data is being drawn. 952 * @param plot the plot (can be used to obtain standard color 953 * information etc). 954 * @param domainAxis the domain axis. 955 * @param rangeAxis the range axis. 956 * @param dataset the dataset. 957 * @param pass the pass. 958 * @param series the series index (zero-based). 959 * @param item the item index (zero-based). 960 */ 961 protected void drawPrimaryLine(XYItemRendererState state, 962 Graphics2D g2, 963 XYPlot plot, 964 XYDataset dataset, 965 int pass, 966 int series, 967 int item, 968 ValueAxis domainAxis, 969 ValueAxis rangeAxis, 970 Rectangle2D dataArea) { 971 if (item == 0) { 972 return; 973 } 974 975 // get the data point... 976 double x1 = dataset.getXValue(series, item); 977 double y1 = dataset.getYValue(series, item); 978 if (Double.isNaN(y1) || Double.isNaN(x1)) { 979 return; 980 } 981 982 double x0 = dataset.getXValue(series, item - 1); 983 double y0 = dataset.getYValue(series, item - 1); 984 if (Double.isNaN(y0) || Double.isNaN(x0)) { 985 return; 986 } 987 988 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 989 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 990 991 double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation); 992 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation); 993 994 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 995 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 996 997 // only draw if we have good values 998 if (Double.isNaN(transX0) || Double.isNaN(transY0) 999 || Double.isNaN(transX1) || Double.isNaN(transY1)) { 1000 return; 1001 } 1002 1003 PlotOrientation orientation = plot.getOrientation(); 1004 boolean visible; 1005 if (orientation == PlotOrientation.HORIZONTAL) { 1006 state.workingLine.setLine(transY0, transX0, transY1, transX1); 1007 } 1008 else if (orientation == PlotOrientation.VERTICAL) { 1009 state.workingLine.setLine(transX0, transY0, transX1, transY1); 1010 } 1011 visible = LineUtilities.clipLine(state.workingLine, dataArea); 1012 if (visible) { 1013 drawFirstPassShape(g2, pass, series, item, state.workingLine); 1014 } 1015 } 1016 1017 /** 1018 * Draws the first pass shape. 1019 * 1020 * @param g2 the graphics device. 1021 * @param pass the pass. 1022 * @param series the series index. 1023 * @param item the item index. 1024 * @param shape the shape. 1025 */ 1026 protected void drawFirstPassShape(Graphics2D g2, int pass, int series, 1027 int item, Shape shape) { 1028 g2.setStroke(getItemStroke(series, item)); 1029 g2.setPaint(getItemPaint(series, item)); 1030 g2.draw(shape); 1031 } 1032 1033 1034 /** 1035 * Draws the item (first pass). This method draws the lines 1036 * connecting the items. Instead of drawing separate lines, 1037 * a GeneralPath is constructed and drawn at the end of 1038 * the series painting. 1039 * 1040 * @param g2 the graphics device. 1041 * @param state the renderer state. 1042 * @param plot the plot (can be used to obtain standard color information 1043 * etc). 1044 * @param dataset the dataset. 1045 * @param pass the pass. 1046 * @param series the series index (zero-based). 1047 * @param item the item index (zero-based). 1048 * @param domainAxis the domain axis. 1049 * @param rangeAxis the range axis. 1050 * @param dataArea the area within which the data is being drawn. 1051 */ 1052 protected void drawPrimaryLineAsPath(XYItemRendererState state, 1053 Graphics2D g2, XYPlot plot, XYDataset dataset, int pass, 1054 int series, int item, ValueAxis domainAxis, ValueAxis rangeAxis, 1055 Rectangle2D dataArea) { 1056 1057 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1058 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1059 1060 // get the data point... 1061 double x1 = dataset.getXValue(series, item); 1062 double y1 = dataset.getYValue(series, item); 1063 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1064 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1065 1066 State s = (State) state; 1067 // update path to reflect latest point 1068 if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 1069 float x = (float) transX1; 1070 float y = (float) transY1; 1071 PlotOrientation orientation = plot.getOrientation(); 1072 if (orientation == PlotOrientation.HORIZONTAL) { 1073 x = (float) transY1; 1074 y = (float) transX1; 1075 } 1076 if (s.isLastPointGood()) { 1077 s.seriesPath.lineTo(x, y); 1078 } 1079 else { 1080 s.seriesPath.moveTo(x, y); 1081 } 1082 s.setLastPointGood(true); 1083 } 1084 else { 1085 s.setLastPointGood(false); 1086 } 1087 // if this is the last item, draw the path ... 1088 if (item == s.getLastItemIndex()) { 1089 // draw path 1090 drawFirstPassShape(g2, pass, series, item, s.seriesPath); 1091 } 1092 } 1093 1094 /** 1095 * Draws the item shapes and adds chart entities (second pass). This method 1096 * draws the shapes which mark the item positions. If <code>entities</code> 1097 * is not <code>null</code> it will be populated with entity information 1098 * for points that fall within the data area. 1099 * 1100 * @param g2 the graphics device. 1101 * @param plot the plot (can be used to obtain standard color 1102 * information etc). 1103 * @param domainAxis the domain axis. 1104 * @param dataArea the area within which the data is being drawn. 1105 * @param rangeAxis the range axis. 1106 * @param dataset the dataset. 1107 * @param pass the pass. 1108 * @param series the series index (zero-based). 1109 * @param item the item index (zero-based). 1110 * @param crosshairState the crosshair state. 1111 * @param entities the entity collection. 1112 */ 1113 protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 1114 XYDataset dataset, int pass, int series, int item, 1115 ValueAxis domainAxis, Rectangle2D dataArea, ValueAxis rangeAxis, 1116 CrosshairState crosshairState, EntityCollection entities) { 1117 1118 Shape entityArea = null; 1119 1120 // get the data point... 1121 double x1 = dataset.getXValue(series, item); 1122 double y1 = dataset.getYValue(series, item); 1123 if (Double.isNaN(y1) || Double.isNaN(x1)) { 1124 return; 1125 } 1126 1127 PlotOrientation orientation = plot.getOrientation(); 1128 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1129 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1130 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1131 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1132 1133 if (getItemShapeVisible(series, item)) { 1134 Shape shape = getItemShape(series, item); 1135 if (orientation == PlotOrientation.HORIZONTAL) { 1136 shape = ShapeUtilities.createTranslatedShape(shape, transY1, 1137 transX1); 1138 } 1139 else if (orientation == PlotOrientation.VERTICAL) { 1140 shape = ShapeUtilities.createTranslatedShape(shape, transX1, 1141 transY1); 1142 } 1143 entityArea = shape; 1144 if (shape.intersects(dataArea)) { 1145 if (getItemShapeFilled(series, item)) { 1146 if (this.useFillPaint) { 1147 g2.setPaint(getItemFillPaint(series, item)); 1148 } 1149 else { 1150 g2.setPaint(getItemPaint(series, item)); 1151 } 1152 g2.fill(shape); 1153 } 1154 if (this.drawOutlines) { 1155 if (getUseOutlinePaint()) { 1156 g2.setPaint(getItemOutlinePaint(series, item)); 1157 } 1158 else { 1159 g2.setPaint(getItemPaint(series, item)); 1160 } 1161 g2.setStroke(getItemOutlineStroke(series, item)); 1162 g2.draw(shape); 1163 } 1164 } 1165 } 1166 1167 double xx = transX1; 1168 double yy = transY1; 1169 if (orientation == PlotOrientation.HORIZONTAL) { 1170 xx = transY1; 1171 yy = transX1; 1172 } 1173 1174 // draw the item label if there is one... 1175 if (isItemLabelVisible(series, item)) { 1176 drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 1177 (y1 < 0.0)); 1178 } 1179 1180 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 1181 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 1182 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 1183 rangeAxisIndex, transX1, transY1, orientation); 1184 1185 // add an entity for the item, but only if it falls within the data 1186 // area... 1187 if (entities != null && isPointInRect(dataArea, xx, yy)) { 1188 addEntity(entities, entityArea, dataset, series, item, xx, yy); 1189 } 1190 } 1191 1192 1193 /** 1194 * Returns a legend item for the specified series. 1195 * 1196 * @param datasetIndex the dataset index (zero-based). 1197 * @param series the series index (zero-based). 1198 * 1199 * @return A legend item for the series (possibly {@code null}). 1200 */ 1201 @Override 1202 public LegendItem getLegendItem(int datasetIndex, int series) { 1203 XYPlot plot = getPlot(); 1204 if (plot == null) { 1205 return null; 1206 } 1207 1208 XYDataset dataset = plot.getDataset(datasetIndex); 1209 if (dataset == null) { 1210 return null; 1211 } 1212 1213 if (!getItemVisible(series, 0)) { 1214 return null; 1215 } 1216 String label = getLegendItemLabelGenerator().generateLabel(dataset, 1217 series); 1218 String description = label; 1219 String toolTipText = null; 1220 if (getLegendItemToolTipGenerator() != null) { 1221 toolTipText = getLegendItemToolTipGenerator().generateLabel( 1222 dataset, series); 1223 } 1224 String urlText = null; 1225 if (getLegendItemURLGenerator() != null) { 1226 urlText = getLegendItemURLGenerator().generateLabel(dataset, 1227 series); 1228 } 1229 boolean shapeIsVisible = getItemShapeVisible(series, 0); 1230 Shape shape = lookupLegendShape(series); 1231 boolean shapeIsFilled = getItemShapeFilled(series, 0); 1232 Paint fillPaint = (this.useFillPaint ? lookupSeriesFillPaint(series) 1233 : lookupSeriesPaint(series)); 1234 boolean shapeOutlineVisible = this.drawOutlines; 1235 Paint outlinePaint = (this.useOutlinePaint ? lookupSeriesOutlinePaint( 1236 series) : lookupSeriesPaint(series)); 1237 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 1238 boolean lineVisible = getItemLineVisible(series, 0); 1239 Stroke lineStroke = lookupSeriesStroke(series); 1240 Paint linePaint = lookupSeriesPaint(series); 1241 LegendItem result = new LegendItem(label, description, toolTipText, 1242 urlText, shapeIsVisible, shape, shapeIsFilled, fillPaint, 1243 shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible, 1244 this.legendLine, lineStroke, linePaint); 1245 result.setLabelFont(lookupLegendTextFont(series)); 1246 Paint labelPaint = lookupLegendTextPaint(series); 1247 if (labelPaint != null) { 1248 result.setLabelPaint(labelPaint); 1249 } 1250 result.setSeriesKey(dataset.getSeriesKey(series)); 1251 result.setSeriesIndex(series); 1252 result.setDataset(dataset); 1253 result.setDatasetIndex(datasetIndex); 1254 1255 return result; 1256 } 1257 1258 /** 1259 * Returns a clone of the renderer. 1260 * 1261 * @return A clone. 1262 * 1263 * @throws CloneNotSupportedException if the clone cannot be created. 1264 */ 1265 @Override 1266 public Object clone() throws CloneNotSupportedException { 1267 XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone(); 1268 clone.seriesLinesVisible 1269 = (BooleanList) this.seriesLinesVisible.clone(); 1270 if (this.legendLine != null) { 1271 clone.legendLine = ShapeUtilities.clone(this.legendLine); 1272 } 1273 clone.seriesShapesVisible 1274 = (BooleanList) this.seriesShapesVisible.clone(); 1275 clone.seriesShapesFilled 1276 = (BooleanList) this.seriesShapesFilled.clone(); 1277 return clone; 1278 } 1279 1280 /** 1281 * Tests this renderer for equality with an arbitrary object. 1282 * 1283 * @param obj the object (<code>null</code> permitted). 1284 * 1285 * @return <code>true</code> or <code>false</code>. 1286 */ 1287 @Override 1288 public boolean equals(Object obj) { 1289 if (obj == this) { 1290 return true; 1291 } 1292 if (!(obj instanceof XYLineAndShapeRenderer)) { 1293 return false; 1294 } 1295 if (!super.equals(obj)) { 1296 return false; 1297 } 1298 XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj; 1299 if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) { 1300 return false; 1301 } 1302 if (!ObjectUtilities.equal( 1303 this.seriesLinesVisible, that.seriesLinesVisible) 1304 ) { 1305 return false; 1306 } 1307 if (this.baseLinesVisible != that.baseLinesVisible) { 1308 return false; 1309 } 1310 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 1311 return false; 1312 } 1313 if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) { 1314 return false; 1315 } 1316 if (!ObjectUtilities.equal( 1317 this.seriesShapesVisible, that.seriesShapesVisible) 1318 ) { 1319 return false; 1320 } 1321 if (this.baseShapesVisible != that.baseShapesVisible) { 1322 return false; 1323 } 1324 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1325 return false; 1326 } 1327 if (!ObjectUtilities.equal( 1328 this.seriesShapesFilled, that.seriesShapesFilled) 1329 ) { 1330 return false; 1331 } 1332 if (this.baseShapesFilled != that.baseShapesFilled) { 1333 return false; 1334 } 1335 if (this.drawOutlines != that.drawOutlines) { 1336 return false; 1337 } 1338 if (this.useOutlinePaint != that.useOutlinePaint) { 1339 return false; 1340 } 1341 if (this.useFillPaint != that.useFillPaint) { 1342 return false; 1343 } 1344 if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) { 1345 return false; 1346 } 1347 return true; 1348 } 1349 1350 /** 1351 * Provides serialization support. 1352 * 1353 * @param stream the input stream. 1354 * 1355 * @throws IOException if there is an I/O error. 1356 * @throws ClassNotFoundException if there is a classpath problem. 1357 */ 1358 private void readObject(ObjectInputStream stream) 1359 throws IOException, ClassNotFoundException { 1360 stream.defaultReadObject(); 1361 this.legendLine = SerialUtilities.readShape(stream); 1362 } 1363 1364 /** 1365 * Provides serialization support. 1366 * 1367 * @param stream the output stream. 1368 * 1369 * @throws IOException if there is an I/O error. 1370 */ 1371 private void writeObject(ObjectOutputStream stream) throws IOException { 1372 stream.defaultWriteObject(); 1373 SerialUtilities.writeShape(this.legendLine, stream); 1374 } 1375 1376}