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