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