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