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