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