001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2011, 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-2009, 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 * 069 */ 070 071package org.jfree.chart.renderer.xy; 072 073import java.awt.Graphics2D; 074import java.awt.Paint; 075import java.awt.Shape; 076import java.awt.Stroke; 077import java.awt.geom.GeneralPath; 078import java.awt.geom.Line2D; 079import java.awt.geom.Rectangle2D; 080import java.io.IOException; 081import java.io.ObjectInputStream; 082import java.io.ObjectOutputStream; 083import java.io.Serializable; 084 085import org.jfree.chart.LegendItem; 086import org.jfree.chart.axis.ValueAxis; 087import org.jfree.chart.entity.EntityCollection; 088import org.jfree.chart.event.RendererChangeEvent; 089import org.jfree.chart.plot.CrosshairState; 090import org.jfree.chart.plot.PlotOrientation; 091import org.jfree.chart.plot.PlotRenderingInfo; 092import org.jfree.chart.plot.XYPlot; 093import org.jfree.chart.util.LineUtilities; 094import org.jfree.data.xy.XYDataset; 095import org.jfree.io.SerialUtilities; 096import org.jfree.ui.RectangleEdge; 097import org.jfree.util.BooleanList; 098import org.jfree.util.BooleanUtilities; 099import org.jfree.util.ObjectUtilities; 100import org.jfree.util.PublicCloneable; 101import org.jfree.util.ShapeUtilities; 102 103/** 104 * A renderer that connects data points with lines and/or draws shapes at each 105 * data point. This renderer is designed for use with the {@link XYPlot} 106 * class. The example shown here is generated by 107 * the <code>XYLineAndShapeRendererDemo2.java</code> program included in the 108 * JFreeChart demo collection: 109 * <br><br> 110 * <img src="../../../../../images/XYLineAndShapeRendererSample.png" 111 * alt="XYLineAndShapeRendererSample.png" /> 112 * 113 */ 114public class XYLineAndShapeRenderer extends AbstractXYItemRenderer 115 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 116 117 /** For serialization. */ 118 private static final long serialVersionUID = -7435246895986425885L; 119 120 /** 121 * A flag that controls whether or not lines are visible for ALL series. 122 * 123 * @deprecated As of 1.0.7. 124 */ 125 private Boolean linesVisible; 126 127 /** 128 * A table of flags that control (per series) whether or not lines are 129 * visible. 130 */ 131 private BooleanList seriesLinesVisible; 132 133 /** The default value returned by the getLinesVisible() method. */ 134 private boolean baseLinesVisible; 135 136 /** The shape that is used to represent a line in the legend. */ 137 private transient Shape legendLine; 138 139 /** 140 * A flag that controls whether or not shapes are visible for ALL series. 141 * 142 * @deprecated As of 1.0.7. 143 */ 144 private Boolean shapesVisible; 145 146 /** 147 * A table of flags that control (per series) whether or not shapes are 148 * visible. 149 */ 150 private BooleanList seriesShapesVisible; 151 152 /** The default value returned by the getShapeVisible() method. */ 153 private boolean baseShapesVisible; 154 155 /** 156 * A flag that controls whether or not shapes are filled for ALL series. 157 * 158 * @deprecated As of 1.0.7. 159 */ 160 private Boolean shapesFilled; 161 162 /** 163 * A table of flags that control (per series) whether or not shapes are 164 * filled. 165 */ 166 private BooleanList seriesShapesFilled; 167 168 /** The default value returned by the getShapeFilled() method. */ 169 private boolean baseShapesFilled; 170 171 /** A flag that controls whether outlines are drawn for shapes. */ 172 private boolean drawOutlines; 173 174 /** 175 * A flag that controls whether the fill paint is used for filling 176 * shapes. 177 */ 178 private boolean useFillPaint; 179 180 /** 181 * A flag that controls whether the outline paint is used for drawing shape 182 * outlines. 183 */ 184 private boolean useOutlinePaint; 185 186 /** 187 * A flag that controls whether or not each series is drawn as a single 188 * path. 189 */ 190 private boolean drawSeriesLineAsPath; 191 192 /** 193 * Creates a new renderer with both lines and shapes visible. 194 */ 195 public XYLineAndShapeRenderer() { 196 this(true, true); 197 } 198 199 /** 200 * Creates a new renderer. 201 * 202 * @param lines lines visible? 203 * @param shapes shapes visible? 204 */ 205 public XYLineAndShapeRenderer(boolean lines, boolean shapes) { 206 this.linesVisible = null; 207 this.seriesLinesVisible = new BooleanList(); 208 this.baseLinesVisible = lines; 209 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 210 211 this.shapesVisible = null; 212 this.seriesShapesVisible = new BooleanList(); 213 this.baseShapesVisible = shapes; 214 215 this.shapesFilled = null; 216 this.useFillPaint = false; // use item paint for fills by default 217 this.seriesShapesFilled = new BooleanList(); 218 this.baseShapesFilled = true; 219 220 this.drawOutlines = true; 221 this.useOutlinePaint = false; // use item paint for outlines by 222 // default, not outline paint 223 224 this.drawSeriesLineAsPath = false; 225 } 226 227 /** 228 * Returns a flag that controls whether or not each series is drawn as a 229 * single path. 230 * 231 * @return A boolean. 232 * 233 * @see #setDrawSeriesLineAsPath(boolean) 234 */ 235 public boolean getDrawSeriesLineAsPath() { 236 return this.drawSeriesLineAsPath; 237 } 238 239 /** 240 * Sets the flag that controls whether or not each series is drawn as a 241 * single path and sends a {@link RendererChangeEvent} to all registered 242 * listeners. 243 * 244 * @param flag the flag. 245 * 246 * @see #getDrawSeriesLineAsPath() 247 */ 248 public void setDrawSeriesLineAsPath(boolean flag) { 249 if (this.drawSeriesLineAsPath != flag) { 250 this.drawSeriesLineAsPath = flag; 251 fireChangeEvent(); 252 } 253 } 254 255 /** 256 * Returns the number of passes through the data that the renderer requires 257 * in order to draw the chart. Most charts will require a single pass, but 258 * some require two passes. 259 * 260 * @return The pass count. 261 */ 262 public int getPassCount() { 263 return 2; 264 } 265 266 // LINES VISIBLE 267 268 /** 269 * Returns the flag used to control whether or not the shape for an item is 270 * visible. 271 * 272 * @param series the series index (zero-based). 273 * @param item the item index (zero-based). 274 * 275 * @return A boolean. 276 */ 277 public boolean getItemLineVisible(int series, int item) { 278 Boolean flag = this.linesVisible; 279 if (flag == null) { 280 flag = getSeriesLinesVisible(series); 281 } 282 if (flag != null) { 283 return flag.booleanValue(); 284 } 285 else { 286 return this.baseLinesVisible; 287 } 288 } 289 290 /** 291 * Returns a flag that controls whether or not lines are drawn for ALL 292 * series. If this flag is <code>null</code>, then the "per series" 293 * settings will apply. 294 * 295 * @return A flag (possibly <code>null</code>). 296 * 297 * @see #setLinesVisible(Boolean) 298 * 299 * @deprecated As of 1.0.7, use the per-series and base level settings. 300 */ 301 public Boolean getLinesVisible() { 302 return this.linesVisible; 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. You need to set this to <code>null</code> if you 309 * want the "per series" settings to apply. 310 * 311 * @param visible the flag (<code>null</code> permitted). 312 * 313 * @see #getLinesVisible() 314 * 315 * @deprecated As of 1.0.7, use the per-series and base level settings. 316 */ 317 public void setLinesVisible(Boolean visible) { 318 this.linesVisible = visible; 319 fireChangeEvent(); 320 } 321 322 /** 323 * Sets a flag that controls whether or not lines are drawn between the 324 * items in ALL series, and sends a {@link RendererChangeEvent} to all 325 * registered listeners. 326 * 327 * @param visible the flag. 328 * 329 * @see #getLinesVisible() 330 * 331 * @deprecated As of 1.0.7, use the per-series and base level settings. 332 */ 333 public void setLinesVisible(boolean visible) { 334 // we use BooleanUtilities here to preserve JRE 1.3.1 compatibility 335 setLinesVisible(BooleanUtilities.valueOf(visible)); 336 } 337 338 /** 339 * Returns the flag used to control whether or not the lines for a series 340 * are visible. 341 * 342 * @param series the series index (zero-based). 343 * 344 * @return The flag (possibly <code>null</code>). 345 * 346 * @see #setSeriesLinesVisible(int, Boolean) 347 */ 348 public Boolean getSeriesLinesVisible(int series) { 349 return this.seriesLinesVisible.getBoolean(series); 350 } 351 352 /** 353 * Sets the 'lines visible' flag for a series and sends a 354 * {@link RendererChangeEvent} to all registered listeners. 355 * 356 * @param series the series index (zero-based). 357 * @param flag the flag (<code>null</code> permitted). 358 * 359 * @see #getSeriesLinesVisible(int) 360 */ 361 public void setSeriesLinesVisible(int series, Boolean flag) { 362 this.seriesLinesVisible.setBoolean(series, flag); 363 fireChangeEvent(); 364 } 365 366 /** 367 * Sets the 'lines visible' flag for a series and sends a 368 * {@link RendererChangeEvent} to all registered listeners. 369 * 370 * @param series the series index (zero-based). 371 * @param visible the flag. 372 * 373 * @see #getSeriesLinesVisible(int) 374 */ 375 public void setSeriesLinesVisible(int series, boolean visible) { 376 setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible)); 377 } 378 379 /** 380 * Returns the base 'lines visible' attribute. 381 * 382 * @return The base flag. 383 * 384 * @see #setBaseLinesVisible(boolean) 385 */ 386 public boolean getBaseLinesVisible() { 387 return this.baseLinesVisible; 388 } 389 390 /** 391 * Sets the base 'lines visible' flag and sends a 392 * {@link RendererChangeEvent} to all registered listeners. 393 * 394 * @param flag the flag. 395 * 396 * @see #getBaseLinesVisible() 397 */ 398 public void setBaseLinesVisible(boolean flag) { 399 this.baseLinesVisible = flag; 400 fireChangeEvent(); 401 } 402 403 /** 404 * Returns the shape used to represent a line in the legend. 405 * 406 * @return The legend line (never <code>null</code>). 407 * 408 * @see #setLegendLine(Shape) 409 */ 410 public Shape getLegendLine() { 411 return this.legendLine; 412 } 413 414 /** 415 * Sets the shape used as a line in each legend item and sends a 416 * {@link RendererChangeEvent} to all registered listeners. 417 * 418 * @param line the line (<code>null</code> not permitted). 419 * 420 * @see #getLegendLine() 421 */ 422 public void setLegendLine(Shape line) { 423 if (line == null) { 424 throw new IllegalArgumentException("Null 'line' argument."); 425 } 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(BooleanUtilities.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, BooleanUtilities.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(BooleanUtilities.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, BooleanUtilities.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 } 799 800 /** 801 * Returns a flag that indicates if the last point drawn (in the 802 * current series) was 'good' (non-null). 803 * 804 * @return A boolean. 805 */ 806 public boolean isLastPointGood() { 807 return this.lastPointGood; 808 } 809 810 /** 811 * Sets a flag that indicates if the last point drawn (in the current 812 * series) was 'good' (non-null). 813 * 814 * @param good the flag. 815 */ 816 public void setLastPointGood(boolean good) { 817 this.lastPointGood = good; 818 } 819 820 /** 821 * This method is called by the {@link XYPlot} at the start of each 822 * series pass. We reset the state for the current series. 823 * 824 * @param dataset the dataset. 825 * @param series the series index. 826 * @param firstItem the first item index for this pass. 827 * @param lastItem the last item index for this pass. 828 * @param pass the current pass index. 829 * @param passCount the number of passes. 830 */ 831 public void startSeriesPass(XYDataset dataset, int series, 832 int firstItem, int lastItem, int pass, int passCount) { 833 this.seriesPath.reset(); 834 this.lastPointGood = false; 835 super.startSeriesPass(dataset, series, firstItem, lastItem, pass, 836 passCount); 837 } 838 839 } 840 841 /** 842 * Initialises the renderer. 843 * <P> 844 * This method will be called before the first item is rendered, giving the 845 * renderer an opportunity to initialise any state information it wants to 846 * maintain. The renderer can do nothing if it chooses. 847 * 848 * @param g2 the graphics device. 849 * @param dataArea the area inside the axes. 850 * @param plot the plot. 851 * @param data the data. 852 * @param info an optional info collection object to return data back to 853 * the caller. 854 * 855 * @return The renderer state. 856 */ 857 public XYItemRendererState initialise(Graphics2D g2, 858 Rectangle2D dataArea, 859 XYPlot plot, 860 XYDataset data, 861 PlotRenderingInfo info) { 862 863 State state = new State(info); 864 state.seriesPath = new GeneralPath(); 865 return state; 866 867 } 868 869 /** 870 * Draws the visual representation of a single data item. 871 * 872 * @param g2 the graphics device. 873 * @param state the renderer state. 874 * @param dataArea the area within which the data is being drawn. 875 * @param info collects information about the drawing. 876 * @param plot the plot (can be used to obtain standard color 877 * information etc). 878 * @param domainAxis the domain axis. 879 * @param rangeAxis the range axis. 880 * @param dataset the dataset. 881 * @param series the series index (zero-based). 882 * @param item the item index (zero-based). 883 * @param crosshairState crosshair information for the plot 884 * (<code>null</code> permitted). 885 * @param pass the pass index. 886 */ 887 public void drawItem(Graphics2D g2, 888 XYItemRendererState state, 889 Rectangle2D dataArea, 890 PlotRenderingInfo info, 891 XYPlot plot, 892 ValueAxis domainAxis, 893 ValueAxis rangeAxis, 894 XYDataset dataset, 895 int series, 896 int item, 897 CrosshairState crosshairState, 898 int pass) { 899 900 // do nothing if item is not visible 901 if (!getItemVisible(series, item)) { 902 return; 903 } 904 905 // first pass draws the background (lines, for instance) 906 if (isLinePass(pass)) { 907 if (getItemLineVisible(series, item)) { 908 if (this.drawSeriesLineAsPath) { 909 drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 910 series, item, domainAxis, rangeAxis, dataArea); 911 } 912 else { 913 drawPrimaryLine(state, g2, plot, dataset, pass, series, 914 item, domainAxis, rangeAxis, dataArea); 915 } 916 } 917 } 918 // second pass adds shapes where the items are .. 919 else if (isItemPass(pass)) { 920 921 // setup for collecting optional entity info... 922 EntityCollection entities = null; 923 if (info != null) { 924 entities = info.getOwner().getEntityCollection(); 925 } 926 927 drawSecondaryPass(g2, plot, dataset, pass, series, item, 928 domainAxis, dataArea, rangeAxis, crosshairState, entities); 929 } 930 } 931 932 /** 933 * Returns <code>true</code> if the specified pass is the one for drawing 934 * lines. 935 * 936 * @param pass the pass. 937 * 938 * @return A boolean. 939 */ 940 protected boolean isLinePass(int pass) { 941 return pass == 0; 942 } 943 944 /** 945 * Returns <code>true</code> if the specified pass is the one for drawing 946 * items. 947 * 948 * @param pass the pass. 949 * 950 * @return A boolean. 951 */ 952 protected boolean isItemPass(int pass) { 953 return pass == 1; 954 } 955 956 /** 957 * Draws the item (first pass). This method draws the lines 958 * connecting the items. 959 * 960 * @param g2 the graphics device. 961 * @param state the renderer state. 962 * @param dataArea the area within which the data is being drawn. 963 * @param plot the plot (can be used to obtain standard color 964 * information etc). 965 * @param domainAxis the domain axis. 966 * @param rangeAxis the range axis. 967 * @param dataset the dataset. 968 * @param pass the pass. 969 * @param series the series index (zero-based). 970 * @param item the item index (zero-based). 971 */ 972 protected void drawPrimaryLine(XYItemRendererState state, 973 Graphics2D g2, 974 XYPlot plot, 975 XYDataset dataset, 976 int pass, 977 int series, 978 int item, 979 ValueAxis domainAxis, 980 ValueAxis rangeAxis, 981 Rectangle2D dataArea) { 982 if (item == 0) { 983 return; 984 } 985 986 // get the data point... 987 double x1 = dataset.getXValue(series, item); 988 double y1 = dataset.getYValue(series, item); 989 if (Double.isNaN(y1) || Double.isNaN(x1)) { 990 return; 991 } 992 993 double x0 = dataset.getXValue(series, item - 1); 994 double y0 = dataset.getYValue(series, item - 1); 995 if (Double.isNaN(y0) || Double.isNaN(x0)) { 996 return; 997 } 998 999 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1000 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1001 1002 double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation); 1003 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation); 1004 1005 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1006 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1007 1008 // only draw if we have good values 1009 if (Double.isNaN(transX0) || Double.isNaN(transY0) 1010 || Double.isNaN(transX1) || Double.isNaN(transY1)) { 1011 return; 1012 } 1013 1014 PlotOrientation orientation = plot.getOrientation(); 1015 boolean visible = false; 1016 if (orientation == PlotOrientation.HORIZONTAL) { 1017 state.workingLine.setLine(transY0, transX0, transY1, transX1); 1018 } 1019 else if (orientation == PlotOrientation.VERTICAL) { 1020 state.workingLine.setLine(transX0, transY0, transX1, transY1); 1021 } 1022 visible = LineUtilities.clipLine(state.workingLine, dataArea); 1023 if (visible) { 1024 drawFirstPassShape(g2, pass, series, item, state.workingLine); 1025 } 1026 } 1027 1028 /** 1029 * Draws the first pass shape. 1030 * 1031 * @param g2 the graphics device. 1032 * @param pass the pass. 1033 * @param series the series index. 1034 * @param item the item index. 1035 * @param shape the shape. 1036 */ 1037 protected void drawFirstPassShape(Graphics2D g2, int pass, int series, 1038 int item, Shape shape) { 1039 g2.setStroke(getItemStroke(series, item)); 1040 g2.setPaint(getItemPaint(series, item)); 1041 g2.draw(shape); 1042 } 1043 1044 1045 /** 1046 * Draws the item (first pass). This method draws the lines 1047 * connecting the items. Instead of drawing separate lines, 1048 * a GeneralPath is constructed and drawn at the end of 1049 * the series painting. 1050 * 1051 * @param g2 the graphics device. 1052 * @param state the renderer state. 1053 * @param plot the plot (can be used to obtain standard color information 1054 * etc). 1055 * @param dataset the dataset. 1056 * @param pass the pass. 1057 * @param series the series index (zero-based). 1058 * @param item the item index (zero-based). 1059 * @param domainAxis the domain axis. 1060 * @param rangeAxis the range axis. 1061 * @param dataArea the area within which the data is being drawn. 1062 */ 1063 protected void drawPrimaryLineAsPath(XYItemRendererState state, 1064 Graphics2D g2, XYPlot plot, 1065 XYDataset dataset, 1066 int pass, 1067 int series, 1068 int item, 1069 ValueAxis domainAxis, 1070 ValueAxis rangeAxis, 1071 Rectangle2D dataArea) { 1072 1073 1074 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1075 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1076 1077 // get the data point... 1078 double x1 = dataset.getXValue(series, item); 1079 double y1 = dataset.getYValue(series, item); 1080 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1081 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1082 1083 State s = (State) state; 1084 // update path to reflect latest point 1085 if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 1086 float x = (float) transX1; 1087 float y = (float) transY1; 1088 PlotOrientation orientation = plot.getOrientation(); 1089 if (orientation == PlotOrientation.HORIZONTAL) { 1090 x = (float) transY1; 1091 y = (float) transX1; 1092 } 1093 if (s.isLastPointGood()) { 1094 s.seriesPath.lineTo(x, y); 1095 } 1096 else { 1097 s.seriesPath.moveTo(x, y); 1098 } 1099 s.setLastPointGood(true); 1100 } 1101 else { 1102 s.setLastPointGood(false); 1103 } 1104 // if this is the last item, draw the path ... 1105 if (item == s.getLastItemIndex()) { 1106 // draw path 1107 drawFirstPassShape(g2, pass, series, item, s.seriesPath); 1108 } 1109 } 1110 1111 /** 1112 * Draws the item shapes and adds chart entities (second pass). This method 1113 * draws the shapes which mark the item positions. If <code>entities</code> 1114 * is not <code>null</code> it will be populated with entity information 1115 * for points that fall within the data area. 1116 * 1117 * @param g2 the graphics device. 1118 * @param plot the plot (can be used to obtain standard color 1119 * information etc). 1120 * @param domainAxis the domain axis. 1121 * @param dataArea the area within which the data is being drawn. 1122 * @param rangeAxis the range axis. 1123 * @param dataset the dataset. 1124 * @param pass the pass. 1125 * @param series the series index (zero-based). 1126 * @param item the item index (zero-based). 1127 * @param crosshairState the crosshair state. 1128 * @param entities the entity collection. 1129 */ 1130 protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 1131 XYDataset dataset, 1132 int pass, int series, int item, 1133 ValueAxis domainAxis, 1134 Rectangle2D dataArea, 1135 ValueAxis rangeAxis, 1136 CrosshairState crosshairState, 1137 EntityCollection entities) { 1138 1139 Shape entityArea = null; 1140 1141 // get the data point... 1142 double x1 = dataset.getXValue(series, item); 1143 double y1 = dataset.getYValue(series, item); 1144 if (Double.isNaN(y1) || Double.isNaN(x1)) { 1145 return; 1146 } 1147 1148 PlotOrientation orientation = plot.getOrientation(); 1149 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1150 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1151 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1152 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1153 1154 if (getItemShapeVisible(series, item)) { 1155 Shape shape = getItemShape(series, item); 1156 if (orientation == PlotOrientation.HORIZONTAL) { 1157 shape = ShapeUtilities.createTranslatedShape(shape, transY1, 1158 transX1); 1159 } 1160 else if (orientation == PlotOrientation.VERTICAL) { 1161 shape = ShapeUtilities.createTranslatedShape(shape, transX1, 1162 transY1); 1163 } 1164 entityArea = shape; 1165 if (shape.intersects(dataArea)) { 1166 if (getItemShapeFilled(series, item)) { 1167 if (this.useFillPaint) { 1168 g2.setPaint(getItemFillPaint(series, item)); 1169 } 1170 else { 1171 g2.setPaint(getItemPaint(series, item)); 1172 } 1173 g2.fill(shape); 1174 } 1175 if (this.drawOutlines) { 1176 if (getUseOutlinePaint()) { 1177 g2.setPaint(getItemOutlinePaint(series, item)); 1178 } 1179 else { 1180 g2.setPaint(getItemPaint(series, item)); 1181 } 1182 g2.setStroke(getItemOutlineStroke(series, item)); 1183 g2.draw(shape); 1184 } 1185 } 1186 } 1187 1188 double xx = transX1; 1189 double yy = transY1; 1190 if (orientation == PlotOrientation.HORIZONTAL) { 1191 xx = transY1; 1192 yy = transX1; 1193 } 1194 1195 // draw the item label if there is one... 1196 if (isItemLabelVisible(series, item)) { 1197 drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 1198 (y1 < 0.0)); 1199 } 1200 1201 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 1202 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 1203 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 1204 rangeAxisIndex, transX1, transY1, orientation); 1205 1206 // add an entity for the item, but only if it falls within the data 1207 // area... 1208 if (entities != null && isPointInRect(dataArea, xx, yy)) { 1209 addEntity(entities, entityArea, dataset, series, item, xx, yy); 1210 } 1211 } 1212 1213 1214 /** 1215 * Returns a legend item for the specified series. 1216 * 1217 * @param datasetIndex the dataset index (zero-based). 1218 * @param series the series index (zero-based). 1219 * 1220 * @return A legend item for the series (possibly <code>null</code). 1221 */ 1222 public LegendItem getLegendItem(int datasetIndex, int series) { 1223 XYPlot plot = getPlot(); 1224 if (plot == null) { 1225 return null; 1226 } 1227 1228 XYDataset dataset = plot.getDataset(datasetIndex); 1229 if (dataset == null) { 1230 return null; 1231 } 1232 1233 if (!getItemVisible(series, 0)) { 1234 return null; 1235 } 1236 String label = getLegendItemLabelGenerator().generateLabel(dataset, 1237 series); 1238 String description = label; 1239 String toolTipText = null; 1240 if (getLegendItemToolTipGenerator() != null) { 1241 toolTipText = getLegendItemToolTipGenerator().generateLabel( 1242 dataset, series); 1243 } 1244 String urlText = null; 1245 if (getLegendItemURLGenerator() != null) { 1246 urlText = getLegendItemURLGenerator().generateLabel(dataset, 1247 series); 1248 } 1249 boolean shapeIsVisible = getItemShapeVisible(series, 0); 1250 Shape shape = lookupLegendShape(series); 1251 boolean shapeIsFilled = getItemShapeFilled(series, 0); 1252 Paint fillPaint = (this.useFillPaint ? lookupSeriesFillPaint(series) 1253 : lookupSeriesPaint(series)); 1254 boolean shapeOutlineVisible = this.drawOutlines; 1255 Paint outlinePaint = (this.useOutlinePaint ? lookupSeriesOutlinePaint( 1256 series) : lookupSeriesPaint(series)); 1257 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 1258 boolean lineVisible = getItemLineVisible(series, 0); 1259 Stroke lineStroke = lookupSeriesStroke(series); 1260 Paint linePaint = lookupSeriesPaint(series); 1261 LegendItem result = new LegendItem(label, description, toolTipText, 1262 urlText, shapeIsVisible, shape, shapeIsFilled, fillPaint, 1263 shapeOutlineVisible, outlinePaint, outlineStroke, lineVisible, 1264 this.legendLine, lineStroke, linePaint); 1265 result.setLabelFont(lookupLegendTextFont(series)); 1266 Paint labelPaint = lookupLegendTextPaint(series); 1267 if (labelPaint != null) { 1268 result.setLabelPaint(labelPaint); 1269 } 1270 result.setSeriesKey(dataset.getSeriesKey(series)); 1271 result.setSeriesIndex(series); 1272 result.setDataset(dataset); 1273 result.setDatasetIndex(datasetIndex); 1274 1275 return result; 1276 } 1277 1278 /** 1279 * Returns a clone of the renderer. 1280 * 1281 * @return A clone. 1282 * 1283 * @throws CloneNotSupportedException if the clone cannot be created. 1284 */ 1285 public Object clone() throws CloneNotSupportedException { 1286 XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone(); 1287 clone.seriesLinesVisible 1288 = (BooleanList) this.seriesLinesVisible.clone(); 1289 if (this.legendLine != null) { 1290 clone.legendLine = ShapeUtilities.clone(this.legendLine); 1291 } 1292 clone.seriesShapesVisible 1293 = (BooleanList) this.seriesShapesVisible.clone(); 1294 clone.seriesShapesFilled 1295 = (BooleanList) this.seriesShapesFilled.clone(); 1296 return clone; 1297 } 1298 1299 /** 1300 * Tests this renderer for equality with an arbitrary object. 1301 * 1302 * @param obj the object (<code>null</code> permitted). 1303 * 1304 * @return <code>true</code> or <code>false</code>. 1305 */ 1306 public boolean equals(Object obj) { 1307 if (obj == this) { 1308 return true; 1309 } 1310 if (!(obj instanceof XYLineAndShapeRenderer)) { 1311 return false; 1312 } 1313 if (!super.equals(obj)) { 1314 return false; 1315 } 1316 XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj; 1317 if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) { 1318 return false; 1319 } 1320 if (!ObjectUtilities.equal( 1321 this.seriesLinesVisible, that.seriesLinesVisible) 1322 ) { 1323 return false; 1324 } 1325 if (this.baseLinesVisible != that.baseLinesVisible) { 1326 return false; 1327 } 1328 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 1329 return false; 1330 } 1331 if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) { 1332 return false; 1333 } 1334 if (!ObjectUtilities.equal( 1335 this.seriesShapesVisible, that.seriesShapesVisible) 1336 ) { 1337 return false; 1338 } 1339 if (this.baseShapesVisible != that.baseShapesVisible) { 1340 return false; 1341 } 1342 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1343 return false; 1344 } 1345 if (!ObjectUtilities.equal( 1346 this.seriesShapesFilled, that.seriesShapesFilled) 1347 ) { 1348 return false; 1349 } 1350 if (this.baseShapesFilled != that.baseShapesFilled) { 1351 return false; 1352 } 1353 if (this.drawOutlines != that.drawOutlines) { 1354 return false; 1355 } 1356 if (this.useOutlinePaint != that.useOutlinePaint) { 1357 return false; 1358 } 1359 if (this.useFillPaint != that.useFillPaint) { 1360 return false; 1361 } 1362 if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) { 1363 return false; 1364 } 1365 return true; 1366 } 1367 1368 /** 1369 * Provides serialization support. 1370 * 1371 * @param stream the input stream. 1372 * 1373 * @throws IOException if there is an I/O error. 1374 * @throws ClassNotFoundException if there is a classpath problem. 1375 */ 1376 private void readObject(ObjectInputStream stream) 1377 throws IOException, ClassNotFoundException { 1378 stream.defaultReadObject(); 1379 this.legendLine = SerialUtilities.readShape(stream); 1380 } 1381 1382 /** 1383 * Provides serialization support. 1384 * 1385 * @param stream the output stream. 1386 * 1387 * @throws IOException if there is an I/O error. 1388 */ 1389 private void writeObject(ObjectOutputStream stream) throws IOException { 1390 stream.defaultWriteObject(); 1391 SerialUtilities.writeShape(this.legendLine, stream); 1392 } 1393 1394}