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. 105 */ 106 public class XYLineAndShapeRenderer extends AbstractXYItemRenderer 107 implements XYItemRenderer, Cloneable, PublicCloneable, 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 * This method is called by the {@link XYPlot} at the start of each 814 * series pass. We reset the state for the current series. 815 * 816 * @param dataset the dataset. 817 * @param series the series index. 818 * @param firstItem the first item index for this pass. 819 * @param lastItem the last item index for this pass. 820 * @param pass the current pass index. 821 * @param passCount the number of passes. 822 */ 823 public void startSeriesPass(XYDataset dataset, int series, 824 int firstItem, int lastItem, int pass, int passCount) { 825 this.seriesPath.reset(); 826 this.lastPointGood = false; 827 super.startSeriesPass(dataset, series, firstItem, lastItem, pass, 828 passCount); 829 } 830 831 } 832 833 /** 834 * Initialises the renderer. 835 * <P> 836 * This method will be called before the first item is rendered, giving the 837 * renderer an opportunity to initialise any state information it wants to 838 * maintain. The renderer can do nothing if it chooses. 839 * 840 * @param g2 the graphics device. 841 * @param dataArea the area inside the axes. 842 * @param plot the plot. 843 * @param data the data. 844 * @param info an optional info collection object to return data back to 845 * the caller. 846 * 847 * @return The renderer state. 848 */ 849 public XYItemRendererState initialise(Graphics2D g2, 850 Rectangle2D dataArea, 851 XYPlot plot, 852 XYDataset data, 853 PlotRenderingInfo info) { 854 855 State state = new State(info); 856 state.seriesPath = new GeneralPath(); 857 return state; 858 859 } 860 861 /** 862 * Draws the visual representation of a single data item. 863 * 864 * @param g2 the graphics device. 865 * @param state the renderer state. 866 * @param dataArea the area within which the data is being drawn. 867 * @param info collects information about the drawing. 868 * @param plot the plot (can be used to obtain standard color 869 * information etc). 870 * @param domainAxis the domain axis. 871 * @param rangeAxis the range axis. 872 * @param dataset the dataset. 873 * @param series the series index (zero-based). 874 * @param item the item index (zero-based). 875 * @param crosshairState crosshair information for the plot 876 * (<code>null</code> permitted). 877 * @param pass the pass index. 878 */ 879 public void drawItem(Graphics2D g2, 880 XYItemRendererState state, 881 Rectangle2D dataArea, 882 PlotRenderingInfo info, 883 XYPlot plot, 884 ValueAxis domainAxis, 885 ValueAxis rangeAxis, 886 XYDataset dataset, 887 int series, 888 int item, 889 CrosshairState crosshairState, 890 int pass) { 891 892 // do nothing if item is not visible 893 if (!getItemVisible(series, item)) { 894 return; 895 } 896 897 // first pass draws the background (lines, for instance) 898 if (isLinePass(pass)) { 899 if (getItemLineVisible(series, item)) { 900 if (this.drawSeriesLineAsPath) { 901 drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 902 series, item, domainAxis, rangeAxis, dataArea); 903 } 904 else { 905 drawPrimaryLine(state, g2, plot, dataset, pass, series, 906 item, domainAxis, rangeAxis, dataArea); 907 } 908 } 909 } 910 // second pass adds shapes where the items are .. 911 else if (isItemPass(pass)) { 912 913 // setup for collecting optional entity info... 914 EntityCollection entities = null; 915 if (info != null) { 916 entities = info.getOwner().getEntityCollection(); 917 } 918 919 drawSecondaryPass(g2, plot, dataset, pass, series, item, 920 domainAxis, dataArea, rangeAxis, crosshairState, entities); 921 } 922 } 923 924 /** 925 * Returns <code>true</code> if the specified pass is the one for drawing 926 * lines. 927 * 928 * @param pass the pass. 929 * 930 * @return A boolean. 931 */ 932 protected boolean isLinePass(int pass) { 933 return pass == 0; 934 } 935 936 /** 937 * Returns <code>true</code> if the specified pass is the one for drawing 938 * items. 939 * 940 * @param pass the pass. 941 * 942 * @return A boolean. 943 */ 944 protected boolean isItemPass(int pass) { 945 return pass == 1; 946 } 947 948 /** 949 * Draws the item (first pass). This method draws the lines 950 * connecting the items. 951 * 952 * @param g2 the graphics device. 953 * @param state the renderer state. 954 * @param dataArea the area within which the data is being drawn. 955 * @param plot the plot (can be used to obtain standard color 956 * information etc). 957 * @param domainAxis the domain axis. 958 * @param rangeAxis the range axis. 959 * @param dataset the dataset. 960 * @param pass the pass. 961 * @param series the series index (zero-based). 962 * @param item the item index (zero-based). 963 */ 964 protected void drawPrimaryLine(XYItemRendererState state, 965 Graphics2D g2, 966 XYPlot plot, 967 XYDataset dataset, 968 int pass, 969 int series, 970 int item, 971 ValueAxis domainAxis, 972 ValueAxis rangeAxis, 973 Rectangle2D dataArea) { 974 if (item == 0) { 975 return; 976 } 977 978 // get the data point... 979 double x1 = dataset.getXValue(series, item); 980 double y1 = dataset.getYValue(series, item); 981 if (Double.isNaN(y1) || Double.isNaN(x1)) { 982 return; 983 } 984 985 double x0 = dataset.getXValue(series, item - 1); 986 double y0 = dataset.getYValue(series, item - 1); 987 if (Double.isNaN(y0) || Double.isNaN(x0)) { 988 return; 989 } 990 991 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 992 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 993 994 double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation); 995 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation); 996 997 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 998 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 999 1000 // only draw if we have good values 1001 if (Double.isNaN(transX0) || Double.isNaN(transY0) 1002 || Double.isNaN(transX1) || Double.isNaN(transY1)) { 1003 return; 1004 } 1005 1006 PlotOrientation orientation = plot.getOrientation(); 1007 if (orientation == PlotOrientation.HORIZONTAL) { 1008 state.workingLine.setLine(transY0, transX0, transY1, transX1); 1009 } 1010 else if (orientation == PlotOrientation.VERTICAL) { 1011 state.workingLine.setLine(transX0, transY0, transX1, transY1); 1012 } 1013 1014 if (state.workingLine.intersects(dataArea)) { 1015 drawFirstPassShape(g2, pass, series, item, state.workingLine); 1016 } 1017 } 1018 1019 /** 1020 * Draws the first pass shape. 1021 * 1022 * @param g2 the graphics device. 1023 * @param pass the pass. 1024 * @param series the series index. 1025 * @param item the item index. 1026 * @param shape the shape. 1027 */ 1028 protected void drawFirstPassShape(Graphics2D g2, int pass, int series, 1029 int item, Shape shape) { 1030 g2.setStroke(getItemStroke(series, item)); 1031 g2.setPaint(getItemPaint(series, item)); 1032 g2.draw(shape); 1033 } 1034 1035 1036 /** 1037 * Draws the item (first pass). This method draws the lines 1038 * connecting the items. Instead of drawing separate lines, 1039 * a GeneralPath is constructed and drawn at the end of 1040 * the series painting. 1041 * 1042 * @param g2 the graphics device. 1043 * @param state the renderer state. 1044 * @param plot the plot (can be used to obtain standard color information 1045 * etc). 1046 * @param dataset the dataset. 1047 * @param pass the pass. 1048 * @param series the series index (zero-based). 1049 * @param item the item index (zero-based). 1050 * @param domainAxis the domain axis. 1051 * @param rangeAxis the range axis. 1052 * @param dataArea the area within which the data is being drawn. 1053 */ 1054 protected void drawPrimaryLineAsPath(XYItemRendererState state, 1055 Graphics2D g2, XYPlot plot, 1056 XYDataset dataset, 1057 int pass, 1058 int series, 1059 int item, 1060 ValueAxis domainAxis, 1061 ValueAxis rangeAxis, 1062 Rectangle2D dataArea) { 1063 1064 1065 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1066 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1067 1068 // get the data point... 1069 double x1 = dataset.getXValue(series, item); 1070 double y1 = dataset.getYValue(series, item); 1071 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1072 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1073 1074 State s = (State) state; 1075 // update path to reflect latest point 1076 if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) { 1077 float x = (float) transX1; 1078 float y = (float) transY1; 1079 PlotOrientation orientation = plot.getOrientation(); 1080 if (orientation == PlotOrientation.HORIZONTAL) { 1081 x = (float) transY1; 1082 y = (float) transX1; 1083 } 1084 if (s.isLastPointGood()) { 1085 s.seriesPath.lineTo(x, y); 1086 } 1087 else { 1088 s.seriesPath.moveTo(x, y); 1089 } 1090 s.setLastPointGood(true); 1091 } 1092 else { 1093 s.setLastPointGood(false); 1094 } 1095 // if this is the last item, draw the path ... 1096 if (item == s.getLastItemIndex()) { 1097 // draw path 1098 drawFirstPassShape(g2, pass, series, item, s.seriesPath); 1099 } 1100 } 1101 1102 /** 1103 * Draws the item shapes and adds chart entities (second pass). This method 1104 * draws the shapes which mark the item positions. If <code>entities</code> 1105 * is not <code>null</code> it will be populated with entity information 1106 * for points that fall within the data area. 1107 * 1108 * @param g2 the graphics device. 1109 * @param plot the plot (can be used to obtain standard color 1110 * information etc). 1111 * @param domainAxis the domain axis. 1112 * @param dataArea the area within which the data is being drawn. 1113 * @param rangeAxis the range axis. 1114 * @param dataset the dataset. 1115 * @param pass the pass. 1116 * @param series the series index (zero-based). 1117 * @param item the item index (zero-based). 1118 * @param crosshairState the crosshair state. 1119 * @param entities the entity collection. 1120 */ 1121 protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 1122 XYDataset dataset, 1123 int pass, int series, int item, 1124 ValueAxis domainAxis, 1125 Rectangle2D dataArea, 1126 ValueAxis rangeAxis, 1127 CrosshairState crosshairState, 1128 EntityCollection entities) { 1129 1130 Shape entityArea = null; 1131 1132 // get the data point... 1133 double x1 = dataset.getXValue(series, item); 1134 double y1 = dataset.getYValue(series, item); 1135 if (Double.isNaN(y1) || Double.isNaN(x1)) { 1136 return; 1137 } 1138 1139 PlotOrientation orientation = plot.getOrientation(); 1140 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 1141 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 1142 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 1143 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 1144 1145 if (getItemShapeVisible(series, item)) { 1146 Shape shape = getItemShape(series, item); 1147 if (orientation == PlotOrientation.HORIZONTAL) { 1148 shape = ShapeUtilities.createTranslatedShape(shape, transY1, 1149 transX1); 1150 } 1151 else if (orientation == PlotOrientation.VERTICAL) { 1152 shape = ShapeUtilities.createTranslatedShape(shape, transX1, 1153 transY1); 1154 } 1155 entityArea = shape; 1156 if (shape.intersects(dataArea)) { 1157 if (getItemShapeFilled(series, item)) { 1158 if (this.useFillPaint) { 1159 g2.setPaint(getItemFillPaint(series, item)); 1160 } 1161 else { 1162 g2.setPaint(getItemPaint(series, item)); 1163 } 1164 g2.fill(shape); 1165 } 1166 if (this.drawOutlines) { 1167 if (getUseOutlinePaint()) { 1168 g2.setPaint(getItemOutlinePaint(series, item)); 1169 } 1170 else { 1171 g2.setPaint(getItemPaint(series, item)); 1172 } 1173 g2.setStroke(getItemOutlineStroke(series, item)); 1174 g2.draw(shape); 1175 } 1176 } 1177 } 1178 1179 double xx = transX1; 1180 double yy = transY1; 1181 if (orientation == PlotOrientation.HORIZONTAL) { 1182 xx = transY1; 1183 yy = transX1; 1184 } 1185 1186 // draw the item label if there is one... 1187 if (isItemLabelVisible(series, item)) { 1188 drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 1189 (y1 < 0.0)); 1190 } 1191 1192 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 1193 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 1194 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 1195 rangeAxisIndex, transX1, transY1, orientation); 1196 1197 // add an entity for the item, but only if it falls within the data 1198 // area... 1199 if (entities != null && isPointInRect(dataArea, xx, yy)) { 1200 addEntity(entities, entityArea, dataset, series, item, xx, yy); 1201 } 1202 } 1203 1204 1205 /** 1206 * Returns a legend item for the specified series. 1207 * 1208 * @param datasetIndex the dataset index (zero-based). 1209 * @param series the series index (zero-based). 1210 * 1211 * @return A legend item for the series. 1212 */ 1213 public LegendItem getLegendItem(int datasetIndex, int series) { 1214 1215 XYPlot plot = getPlot(); 1216 if (plot == null) { 1217 return null; 1218 } 1219 1220 LegendItem result = null; 1221 XYDataset dataset = plot.getDataset(datasetIndex); 1222 if (dataset != null) { 1223 if (getItemVisible(series, 0)) { 1224 String label = getLegendItemLabelGenerator().generateLabel( 1225 dataset, series); 1226 String description = label; 1227 String toolTipText = null; 1228 if (getLegendItemToolTipGenerator() != null) { 1229 toolTipText = getLegendItemToolTipGenerator().generateLabel( 1230 dataset, series); 1231 } 1232 String urlText = null; 1233 if (getLegendItemURLGenerator() != null) { 1234 urlText = getLegendItemURLGenerator().generateLabel( 1235 dataset, series); 1236 } 1237 boolean shapeIsVisible = getItemShapeVisible(series, 0); 1238 Shape shape = lookupLegendShape(series); 1239 boolean shapeIsFilled = getItemShapeFilled(series, 0); 1240 Paint fillPaint = (this.useFillPaint 1241 ? lookupSeriesFillPaint(series) 1242 : lookupSeriesPaint(series)); 1243 boolean shapeOutlineVisible = this.drawOutlines; 1244 Paint outlinePaint = (this.useOutlinePaint 1245 ? lookupSeriesOutlinePaint(series) 1246 : lookupSeriesPaint(series)); 1247 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 1248 boolean lineVisible = getItemLineVisible(series, 0); 1249 Stroke lineStroke = lookupSeriesStroke(series); 1250 Paint linePaint = lookupSeriesPaint(series); 1251 result = new LegendItem(label, description, toolTipText, 1252 urlText, shapeIsVisible, shape, shapeIsFilled, 1253 fillPaint, shapeOutlineVisible, outlinePaint, 1254 outlineStroke, lineVisible, this.legendLine, 1255 lineStroke, linePaint); 1256 result.setLabelFont(lookupLegendTextFont(series)); 1257 Paint labelPaint = lookupLegendTextPaint(series); 1258 if (labelPaint != null) { 1259 result.setLabelPaint(labelPaint); 1260 } 1261 result.setSeriesKey(dataset.getSeriesKey(series)); 1262 result.setSeriesIndex(series); 1263 result.setDataset(dataset); 1264 result.setDatasetIndex(datasetIndex); 1265 } 1266 } 1267 1268 return result; 1269 1270 } 1271 1272 /** 1273 * Returns a clone of the renderer. 1274 * 1275 * @return A clone. 1276 * 1277 * @throws CloneNotSupportedException if the clone cannot be created. 1278 */ 1279 public Object clone() throws CloneNotSupportedException { 1280 XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone(); 1281 clone.seriesLinesVisible 1282 = (BooleanList) this.seriesLinesVisible.clone(); 1283 if (this.legendLine != null) { 1284 clone.legendLine = ShapeUtilities.clone(this.legendLine); 1285 } 1286 clone.seriesShapesVisible 1287 = (BooleanList) this.seriesShapesVisible.clone(); 1288 clone.seriesShapesFilled 1289 = (BooleanList) this.seriesShapesFilled.clone(); 1290 return clone; 1291 } 1292 1293 /** 1294 * Tests this renderer for equality with an arbitrary object. 1295 * 1296 * @param obj the object (<code>null</code> permitted). 1297 * 1298 * @return <code>true</code> or <code>false</code>. 1299 */ 1300 public boolean equals(Object obj) { 1301 if (obj == this) { 1302 return true; 1303 } 1304 if (!(obj instanceof XYLineAndShapeRenderer)) { 1305 return false; 1306 } 1307 if (!super.equals(obj)) { 1308 return false; 1309 } 1310 XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj; 1311 if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) { 1312 return false; 1313 } 1314 if (!ObjectUtilities.equal( 1315 this.seriesLinesVisible, that.seriesLinesVisible) 1316 ) { 1317 return false; 1318 } 1319 if (this.baseLinesVisible != that.baseLinesVisible) { 1320 return false; 1321 } 1322 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 1323 return false; 1324 } 1325 if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) { 1326 return false; 1327 } 1328 if (!ObjectUtilities.equal( 1329 this.seriesShapesVisible, that.seriesShapesVisible) 1330 ) { 1331 return false; 1332 } 1333 if (this.baseShapesVisible != that.baseShapesVisible) { 1334 return false; 1335 } 1336 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1337 return false; 1338 } 1339 if (!ObjectUtilities.equal( 1340 this.seriesShapesFilled, that.seriesShapesFilled) 1341 ) { 1342 return false; 1343 } 1344 if (this.baseShapesFilled != that.baseShapesFilled) { 1345 return false; 1346 } 1347 if (this.drawOutlines != that.drawOutlines) { 1348 return false; 1349 } 1350 if (this.useOutlinePaint != that.useOutlinePaint) { 1351 return false; 1352 } 1353 if (this.useFillPaint != that.useFillPaint) { 1354 return false; 1355 } 1356 if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) { 1357 return false; 1358 } 1359 return true; 1360 } 1361 1362 /** 1363 * Provides serialization support. 1364 * 1365 * @param stream the input stream. 1366 * 1367 * @throws IOException if there is an I/O error. 1368 * @throws ClassNotFoundException if there is a classpath problem. 1369 */ 1370 private void readObject(ObjectInputStream stream) 1371 throws IOException, ClassNotFoundException { 1372 stream.defaultReadObject(); 1373 this.legendLine = SerialUtilities.readShape(stream); 1374 } 1375 1376 /** 1377 * Provides serialization support. 1378 * 1379 * @param stream the output stream. 1380 * 1381 * @throws IOException if there is an I/O error. 1382 */ 1383 private void writeObject(ObjectOutputStream stream) throws IOException { 1384 stream.defaultWriteObject(); 1385 SerialUtilities.writeShape(this.legendLine, stream); 1386 } 1387 1388 }