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 * StandardXYItemRenderer.java 029 * --------------------------- 030 * (C) Copyright 2001-2008, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Mark Watson (www.markwatson.com); 034 * Jonathan Nash; 035 * Andreas Schneider; 036 * Norbert Kiesel (for TBD Networks); 037 * Christian W. Zuckschwerdt; 038 * Bill Kelemen; 039 * Nicolas Brodu (for Astrium and EADS Corporate Research 040 * Center); 041 * 042 * Changes: 043 * -------- 044 * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG); 045 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG); 046 * 21-Dec-2001 : Added working line instance to improve performance (DG); 047 * 22-Jan-2002 : Added code to lock crosshairs to data points. Based on code 048 * by Jonathan Nash (DG); 049 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 050 * 28-Mar-2002 : Added a property change listener mechanism so that the 051 * renderer no longer needs to be immutable (DG); 052 * 02-Apr-2002 : Modified to handle null values (DG); 053 * 09-Apr-2002 : Modified draw method to return void. Removed the translated 054 * zero from the drawItem method. Override the initialise() 055 * method to calculate it (DG); 056 * 13-May-2002 : Added code from Andreas Schneider to allow changing 057 * shapes/colors per item (DG); 058 * 24-May-2002 : Incorporated tooltips into chart entities (DG); 059 * 25-Jun-2002 : Removed redundant code (DG); 060 * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA); 061 * 08-Aug-2002 : Added discontinuous lines option contributed by 062 * Norbert Kiesel (DG); 063 * 20-Aug-2002 : Added user definable default values to be returned by 064 * protected methods unless overridden by a subclass (DG); 065 * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG); 066 * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG); 067 * 25-Mar-2003 : Implemented Serializable (DG); 068 * 01-May-2003 : Modified drawItem() method signature (DG); 069 * 15-May-2003 : Modified to take into account the plot orientation (DG); 070 * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG); 071 * 30-Jul-2003 : Modified entity constructor (CZ); 072 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 073 * 24-Aug-2003 : Added null/NaN checks in drawItem (BK); 074 * 08-Sep-2003 : Fixed serialization (NB); 075 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 076 * 21-Jan-2004 : Override for getLegendItem() method (DG); 077 * 27-Jan-2004 : Moved working line into state object (DG); 078 * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding 079 * easier (DG); 080 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed 081 * XYToolTipGenerator --> XYItemLabelGenerator (DG); 082 * 08-Jun-2004 : Modified to use getX() and getY() methods (DG); 083 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 084 * getYValue() (DG); 085 * 25-Aug-2004 : Created addEntity() method in superclass (DG); 086 * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG); 087 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG); 088 * 23-Feb-2005 : Fixed getLegendItem() method to show lines. Fixed bug 089 * 1077108 (shape not visible for first item in series) (DG); 090 * 10-Apr-2005 : Fixed item label positioning with horizontal orientation (DG); 091 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG); 092 * 27-Apr-2005 : Use generator for series label in legend (DG); 093 * ------------- JFREECHART 1.0.x --------------------------------------------- 094 * 15-Jun-2006 : Fixed bug (1380480) for rendering series as path (DG); 095 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG); 096 * 14-Mar-2007 : Fixed problems with the equals() and clone() methods (DG); 097 * 23-Mar-2007 : Clean-up of shapesFilled attributes (DG); 098 * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer 099 * change (DG); 100 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() 101 * method (DG); 102 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG); 103 * 08-Jun-2007 : Fixed bug in entity creation (DG); 104 * 21-Nov-2007 : Deprecated override flag methods (DG); 105 * 02-Jun-2008 : Fixed tooltips for data items at lower edges of data area (DG); 106 * 107 */ 108 109 package org.jfree.chart.renderer.xy; 110 111 import java.awt.Graphics2D; 112 import java.awt.Image; 113 import java.awt.Paint; 114 import java.awt.Point; 115 import java.awt.Shape; 116 import java.awt.Stroke; 117 import java.awt.geom.GeneralPath; 118 import java.awt.geom.Line2D; 119 import java.awt.geom.Rectangle2D; 120 import java.io.IOException; 121 import java.io.ObjectInputStream; 122 import java.io.ObjectOutputStream; 123 import java.io.Serializable; 124 125 import org.jfree.chart.LegendItem; 126 import org.jfree.chart.axis.ValueAxis; 127 import org.jfree.chart.entity.EntityCollection; 128 import org.jfree.chart.event.RendererChangeEvent; 129 import org.jfree.chart.labels.XYToolTipGenerator; 130 import org.jfree.chart.plot.CrosshairState; 131 import org.jfree.chart.plot.Plot; 132 import org.jfree.chart.plot.PlotOrientation; 133 import org.jfree.chart.plot.PlotRenderingInfo; 134 import org.jfree.chart.plot.XYPlot; 135 import org.jfree.chart.urls.XYURLGenerator; 136 import org.jfree.data.xy.XYDataset; 137 import org.jfree.io.SerialUtilities; 138 import org.jfree.ui.RectangleEdge; 139 import org.jfree.util.BooleanList; 140 import org.jfree.util.BooleanUtilities; 141 import org.jfree.util.ObjectUtilities; 142 import org.jfree.util.PublicCloneable; 143 import org.jfree.util.ShapeUtilities; 144 import org.jfree.util.UnitType; 145 146 /** 147 * Standard item renderer for an {@link XYPlot}. This class can draw (a) 148 * shapes at each point, or (b) lines between points, or (c) both shapes and 149 * lines. 150 * <P> 151 * This renderer has been retained for historical reasons and, in general, you 152 * should use the {@link XYLineAndShapeRenderer} class instead. 153 */ 154 public class StandardXYItemRenderer extends AbstractXYItemRenderer 155 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 156 157 /** For serialization. */ 158 private static final long serialVersionUID = -3271351259436865995L; 159 160 /** Constant for the type of rendering (shapes only). */ 161 public static final int SHAPES = 1; 162 163 /** Constant for the type of rendering (lines only). */ 164 public static final int LINES = 2; 165 166 /** Constant for the type of rendering (shapes and lines). */ 167 public static final int SHAPES_AND_LINES = SHAPES | LINES; 168 169 /** Constant for the type of rendering (images only). */ 170 public static final int IMAGES = 4; 171 172 /** Constant for the type of rendering (discontinuous lines). */ 173 public static final int DISCONTINUOUS = 8; 174 175 /** Constant for the type of rendering (discontinuous lines). */ 176 public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS; 177 178 /** A flag indicating whether or not shapes are drawn at each XY point. */ 179 private boolean baseShapesVisible; 180 181 /** A flag indicating whether or not lines are drawn between XY points. */ 182 private boolean plotLines; 183 184 /** A flag indicating whether or not images are drawn between XY points. */ 185 private boolean plotImages; 186 187 /** A flag controlling whether or not discontinuous lines are used. */ 188 private boolean plotDiscontinuous; 189 190 /** Specifies how the gap threshold value is interpreted. */ 191 private UnitType gapThresholdType = UnitType.RELATIVE; 192 193 /** Threshold for deciding when to discontinue a line. */ 194 private double gapThreshold = 1.0; 195 196 /** 197 * A flag that controls whether or not shapes are filled for ALL series. 198 * 199 * @deprecated As of 1.0.8, this override should not be used. 200 */ 201 private Boolean shapesFilled; 202 203 /** 204 * A table of flags that control (per series) whether or not shapes are 205 * filled. 206 */ 207 private BooleanList seriesShapesFilled; 208 209 /** The default value returned by the getShapeFilled() method. */ 210 private boolean baseShapesFilled; 211 212 /** 213 * A flag that controls whether or not each series is drawn as a single 214 * path. 215 */ 216 private boolean drawSeriesLineAsPath; 217 218 /** 219 * The shape that is used to represent a line in the legend. 220 * This should never be set to <code>null</code>. 221 */ 222 private transient Shape legendLine; 223 224 /** 225 * Constructs a new renderer. 226 */ 227 public StandardXYItemRenderer() { 228 this(LINES, null); 229 } 230 231 /** 232 * Constructs a new renderer. To specify the type of renderer, use one of 233 * the constants: {@link #SHAPES}, {@link #LINES} or 234 * {@link #SHAPES_AND_LINES}. 235 * 236 * @param type the type. 237 */ 238 public StandardXYItemRenderer(int type) { 239 this(type, null); 240 } 241 242 /** 243 * Constructs a new renderer. To specify the type of renderer, use one of 244 * the constants: {@link #SHAPES}, {@link #LINES} or 245 * {@link #SHAPES_AND_LINES}. 246 * 247 * @param type the type of renderer. 248 * @param toolTipGenerator the item label generator (<code>null</code> 249 * permitted). 250 */ 251 public StandardXYItemRenderer(int type, 252 XYToolTipGenerator toolTipGenerator) { 253 this(type, toolTipGenerator, null); 254 } 255 256 /** 257 * Constructs a new renderer. To specify the type of renderer, use one of 258 * the constants: {@link #SHAPES}, {@link #LINES} or 259 * {@link #SHAPES_AND_LINES}. 260 * 261 * @param type the type of renderer. 262 * @param toolTipGenerator the item label generator (<code>null</code> 263 * permitted). 264 * @param urlGenerator the URL generator. 265 */ 266 public StandardXYItemRenderer(int type, 267 XYToolTipGenerator toolTipGenerator, 268 XYURLGenerator urlGenerator) { 269 270 super(); 271 setBaseToolTipGenerator(toolTipGenerator); 272 setURLGenerator(urlGenerator); 273 if ((type & SHAPES) != 0) { 274 this.baseShapesVisible = true; 275 } 276 if ((type & LINES) != 0) { 277 this.plotLines = true; 278 } 279 if ((type & IMAGES) != 0) { 280 this.plotImages = true; 281 } 282 if ((type & DISCONTINUOUS) != 0) { 283 this.plotDiscontinuous = true; 284 } 285 286 this.shapesFilled = null; 287 this.seriesShapesFilled = new BooleanList(); 288 this.baseShapesFilled = true; 289 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 290 this.drawSeriesLineAsPath = false; 291 } 292 293 /** 294 * Returns true if shapes are being plotted by the renderer. 295 * 296 * @return <code>true</code> if shapes are being plotted by the renderer. 297 * 298 * @see #setBaseShapesVisible 299 */ 300 public boolean getBaseShapesVisible() { 301 return this.baseShapesVisible; 302 } 303 304 /** 305 * Sets the flag that controls whether or not a shape is plotted at each 306 * data point. 307 * 308 * @param flag the flag. 309 * 310 * @see #getBaseShapesVisible 311 */ 312 public void setBaseShapesVisible(boolean flag) { 313 if (this.baseShapesVisible != flag) { 314 this.baseShapesVisible = flag; 315 fireChangeEvent(); 316 } 317 } 318 319 // SHAPES FILLED 320 321 /** 322 * Returns the flag used to control whether or not the shape for an item is 323 * filled. 324 * <p> 325 * The default implementation passes control to the 326 * <code>getSeriesShapesFilled</code> method. You can override this method 327 * if you require different behaviour. 328 * 329 * @param series the series index (zero-based). 330 * @param item the item index (zero-based). 331 * 332 * @return A boolean. 333 * 334 * @see #getSeriesShapesFilled(int) 335 */ 336 public boolean getItemShapeFilled(int series, int item) { 337 // return the overall setting, if there is one... 338 if (this.shapesFilled != null) { 339 return this.shapesFilled.booleanValue(); 340 } 341 342 // otherwise look up the paint table 343 Boolean flag = this.seriesShapesFilled.getBoolean(series); 344 if (flag != null) { 345 return flag.booleanValue(); 346 } 347 else { 348 return this.baseShapesFilled; 349 } 350 } 351 352 /** 353 * Returns the override flag that controls whether or not shapes are filled 354 * for ALL series. 355 * 356 * @return The flag (possibly <code>null</code>). 357 * 358 * @since 1.0.5 359 * 360 * @deprecated As of 1.0.8, you should avoid using this method and rely 361 * on just the per-series ({@link #getSeriesShapesFilled(int)}) 362 * and base-level ({@link #getBaseShapesFilled()}) settings. 363 */ 364 public Boolean getShapesFilled() { 365 return this.shapesFilled; 366 } 367 368 /** 369 * Sets the override flag that controls whether or not shapes are filled 370 * for ALL series and sends a {@link RendererChangeEvent} to all registered 371 * listeners. 372 * 373 * @param filled the flag. 374 * 375 * @see #setShapesFilled(Boolean) 376 * 377 * @deprecated As of 1.0.8, you should avoid using this method and rely 378 * on just the per-series ({@link #setSeriesShapesFilled(int, 379 * Boolean)}) and base-level ({@link #setBaseShapesVisible( 380 * boolean)}) settings. 381 */ 382 public void setShapesFilled(boolean filled) { 383 // here we use BooleanUtilities to remain compatible with JDKs < 1.4 384 setShapesFilled(BooleanUtilities.valueOf(filled)); 385 } 386 387 /** 388 * Sets the override flag that controls whether or not shapes are filled 389 * for ALL series and sends a {@link RendererChangeEvent} to all registered 390 * listeners. 391 * 392 * @param filled the flag (<code>null</code> permitted). 393 * 394 * @see #setShapesFilled(boolean) 395 * 396 * @deprecated As of 1.0.8, you should avoid using this method and rely 397 * on just the per-series ({@link #setSeriesShapesFilled(int, 398 * Boolean)}) and base-level ({@link #setBaseShapesVisible( 399 * boolean)}) settings. 400 */ 401 public void setShapesFilled(Boolean filled) { 402 this.shapesFilled = filled; 403 fireChangeEvent(); 404 } 405 406 /** 407 * Returns the flag used to control whether or not the shapes for a series 408 * are filled. 409 * 410 * @param series the series index (zero-based). 411 * 412 * @return A boolean. 413 */ 414 public Boolean getSeriesShapesFilled(int series) { 415 return this.seriesShapesFilled.getBoolean(series); 416 } 417 418 /** 419 * Sets the 'shapes filled' flag for a series and sends a 420 * {@link RendererChangeEvent} to all registered listeners. 421 * 422 * @param series the series index (zero-based). 423 * @param flag the flag. 424 * 425 * @see #getSeriesShapesFilled(int) 426 */ 427 public void setSeriesShapesFilled(int series, Boolean flag) { 428 this.seriesShapesFilled.setBoolean(series, flag); 429 fireChangeEvent(); 430 } 431 432 /** 433 * Returns the base 'shape filled' attribute. 434 * 435 * @return The base flag. 436 * 437 * @see #setBaseShapesFilled(boolean) 438 */ 439 public boolean getBaseShapesFilled() { 440 return this.baseShapesFilled; 441 } 442 443 /** 444 * Sets the base 'shapes filled' flag and sends a 445 * {@link RendererChangeEvent} to all registered listeners. 446 * 447 * @param flag the flag. 448 * 449 * @see #getBaseShapesFilled() 450 */ 451 public void setBaseShapesFilled(boolean flag) { 452 this.baseShapesFilled = flag; 453 } 454 455 /** 456 * Returns true if lines are being plotted by the renderer. 457 * 458 * @return <code>true</code> if lines are being plotted by the renderer. 459 * 460 * @see #setPlotLines(boolean) 461 */ 462 public boolean getPlotLines() { 463 return this.plotLines; 464 } 465 466 /** 467 * Sets the flag that controls whether or not a line is plotted between 468 * each data point and sends a {@link RendererChangeEvent} to all 469 * registered listeners. 470 * 471 * @param flag the flag. 472 * 473 * @see #getPlotLines() 474 */ 475 public void setPlotLines(boolean flag) { 476 if (this.plotLines != flag) { 477 this.plotLines = flag; 478 fireChangeEvent(); 479 } 480 } 481 482 /** 483 * Returns the gap threshold type (relative or absolute). 484 * 485 * @return The type. 486 * 487 * @see #setGapThresholdType(UnitType) 488 */ 489 public UnitType getGapThresholdType() { 490 return this.gapThresholdType; 491 } 492 493 /** 494 * Sets the gap threshold type and sends a {@link RendererChangeEvent} to 495 * all registered listeners. 496 * 497 * @param thresholdType the type (<code>null</code> not permitted). 498 * 499 * @see #getGapThresholdType() 500 */ 501 public void setGapThresholdType(UnitType thresholdType) { 502 if (thresholdType == null) { 503 throw new IllegalArgumentException( 504 "Null 'thresholdType' argument."); 505 } 506 this.gapThresholdType = thresholdType; 507 fireChangeEvent(); 508 } 509 510 /** 511 * Returns the gap threshold for discontinuous lines. 512 * 513 * @return The gap threshold. 514 * 515 * @see #setGapThreshold(double) 516 */ 517 public double getGapThreshold() { 518 return this.gapThreshold; 519 } 520 521 /** 522 * Sets the gap threshold for discontinuous lines and sends a 523 * {@link RendererChangeEvent} to all registered listeners. 524 * 525 * @param t the threshold. 526 * 527 * @see #getGapThreshold() 528 */ 529 public void setGapThreshold(double t) { 530 this.gapThreshold = t; 531 fireChangeEvent(); 532 } 533 534 /** 535 * Returns true if images are being plotted by the renderer. 536 * 537 * @return <code>true</code> if images are being plotted by the renderer. 538 * 539 * @see #setPlotImages(boolean) 540 */ 541 public boolean getPlotImages() { 542 return this.plotImages; 543 } 544 545 /** 546 * Sets the flag that controls whether or not an image is drawn at each 547 * data point and sends a {@link RendererChangeEvent} to all registered 548 * listeners. 549 * 550 * @param flag the flag. 551 * 552 * @see #getPlotImages() 553 */ 554 public void setPlotImages(boolean flag) { 555 if (this.plotImages != flag) { 556 this.plotImages = flag; 557 fireChangeEvent(); 558 } 559 } 560 561 /** 562 * Returns a flag that controls whether or not the renderer shows 563 * discontinuous lines. 564 * 565 * @return <code>true</code> if lines should be discontinuous. 566 */ 567 public boolean getPlotDiscontinuous() { 568 return this.plotDiscontinuous; 569 } 570 571 /** 572 * Sets the flag that controls whether or not the renderer shows 573 * discontinuous lines, and sends a {@link RendererChangeEvent} to all 574 * registered listeners. 575 * 576 * @param flag the new flag value. 577 * 578 * @since 1.0.5 579 */ 580 public void setPlotDiscontinuous(boolean flag) { 581 if (this.plotDiscontinuous != flag) { 582 this.plotDiscontinuous = flag; 583 fireChangeEvent(); 584 } 585 } 586 587 /** 588 * Returns a flag that controls whether or not each series is drawn as a 589 * single path. 590 * 591 * @return A boolean. 592 * 593 * @see #setDrawSeriesLineAsPath(boolean) 594 */ 595 public boolean getDrawSeriesLineAsPath() { 596 return this.drawSeriesLineAsPath; 597 } 598 599 /** 600 * Sets the flag that controls whether or not each series is drawn as a 601 * single path. 602 * 603 * @param flag the flag. 604 * 605 * @see #getDrawSeriesLineAsPath() 606 */ 607 public void setDrawSeriesLineAsPath(boolean flag) { 608 this.drawSeriesLineAsPath = flag; 609 } 610 611 /** 612 * Returns the shape used to represent a line in the legend. 613 * 614 * @return The legend line (never <code>null</code>). 615 * 616 * @see #setLegendLine(Shape) 617 */ 618 public Shape getLegendLine() { 619 return this.legendLine; 620 } 621 622 /** 623 * Sets the shape used as a line in each legend item and sends a 624 * {@link RendererChangeEvent} to all registered listeners. 625 * 626 * @param line the line (<code>null</code> not permitted). 627 * 628 * @see #getLegendLine() 629 */ 630 public void setLegendLine(Shape line) { 631 if (line == null) { 632 throw new IllegalArgumentException("Null 'line' argument."); 633 } 634 this.legendLine = line; 635 fireChangeEvent(); 636 } 637 638 /** 639 * Returns a legend item for a series. 640 * 641 * @param datasetIndex the dataset index (zero-based). 642 * @param series the series index (zero-based). 643 * 644 * @return A legend item for the series. 645 */ 646 public LegendItem getLegendItem(int datasetIndex, int series) { 647 XYPlot plot = getPlot(); 648 if (plot == null) { 649 return null; 650 } 651 LegendItem result = null; 652 XYDataset dataset = plot.getDataset(datasetIndex); 653 if (dataset != null) { 654 if (getItemVisible(series, 0)) { 655 String label = getLegendItemLabelGenerator().generateLabel( 656 dataset, series); 657 String description = label; 658 String toolTipText = null; 659 if (getLegendItemToolTipGenerator() != null) { 660 toolTipText = getLegendItemToolTipGenerator().generateLabel( 661 dataset, series); 662 } 663 String urlText = null; 664 if (getLegendItemURLGenerator() != null) { 665 urlText = getLegendItemURLGenerator().generateLabel( 666 dataset, series); 667 } 668 Shape shape = lookupSeriesShape(series); 669 boolean shapeFilled = getItemShapeFilled(series, 0); 670 Paint paint = lookupSeriesPaint(series); 671 Paint linePaint = paint; 672 Stroke lineStroke = lookupSeriesStroke(series); 673 result = new LegendItem(label, description, toolTipText, 674 urlText, this.baseShapesVisible, shape, shapeFilled, 675 paint, !shapeFilled, paint, lineStroke, 676 this.plotLines, this.legendLine, lineStroke, linePaint); 677 result.setDataset(dataset); 678 result.setDatasetIndex(datasetIndex); 679 result.setSeriesKey(dataset.getSeriesKey(series)); 680 result.setSeriesIndex(series); 681 } 682 } 683 return result; 684 } 685 686 /** 687 * Records the state for the renderer. This is used to preserve state 688 * information between calls to the drawItem() method for a single chart 689 * drawing. 690 */ 691 public static class State extends XYItemRendererState { 692 693 /** The path for the current series. */ 694 public GeneralPath seriesPath; 695 696 /** The series index. */ 697 private int seriesIndex; 698 699 /** 700 * A flag that indicates if the last (x, y) point was 'good' 701 * (non-null). 702 */ 703 private boolean lastPointGood; 704 705 /** 706 * Creates a new state instance. 707 * 708 * @param info the plot rendering info. 709 */ 710 public State(PlotRenderingInfo info) { 711 super(info); 712 } 713 714 /** 715 * Returns a flag that indicates if the last point drawn (in the 716 * current series) was 'good' (non-null). 717 * 718 * @return A boolean. 719 */ 720 public boolean isLastPointGood() { 721 return this.lastPointGood; 722 } 723 724 /** 725 * Sets a flag that indicates if the last point drawn (in the current 726 * series) was 'good' (non-null). 727 * 728 * @param good the flag. 729 */ 730 public void setLastPointGood(boolean good) { 731 this.lastPointGood = good; 732 } 733 734 /** 735 * Returns the series index for the current path. 736 * 737 * @return The series index for the current path. 738 */ 739 public int getSeriesIndex() { 740 return this.seriesIndex; 741 } 742 743 /** 744 * Sets the series index for the current path. 745 * 746 * @param index the index. 747 */ 748 public void setSeriesIndex(int index) { 749 this.seriesIndex = index; 750 } 751 } 752 753 /** 754 * Initialises the renderer. 755 * <P> 756 * This method will be called before the first item is rendered, giving the 757 * renderer an opportunity to initialise any state information it wants to 758 * maintain. The renderer can do nothing if it chooses. 759 * 760 * @param g2 the graphics device. 761 * @param dataArea the area inside the axes. 762 * @param plot the plot. 763 * @param data the data. 764 * @param info an optional info collection object to return data back to 765 * the caller. 766 * 767 * @return The renderer state. 768 */ 769 public XYItemRendererState initialise(Graphics2D g2, 770 Rectangle2D dataArea, 771 XYPlot plot, 772 XYDataset data, 773 PlotRenderingInfo info) { 774 775 State state = new State(info); 776 state.seriesPath = new GeneralPath(); 777 state.seriesIndex = -1; 778 return state; 779 780 } 781 782 /** 783 * Draws the visual representation of a single data item. 784 * 785 * @param g2 the graphics device. 786 * @param state the renderer state. 787 * @param dataArea the area within which the data is being drawn. 788 * @param info collects information about the drawing. 789 * @param plot the plot (can be used to obtain standard color information 790 * etc). 791 * @param domainAxis the domain axis. 792 * @param rangeAxis the range axis. 793 * @param dataset the dataset. 794 * @param series the series index (zero-based). 795 * @param item the item index (zero-based). 796 * @param crosshairState crosshair information for the plot 797 * (<code>null</code> permitted). 798 * @param pass the pass index. 799 */ 800 public void drawItem(Graphics2D g2, 801 XYItemRendererState state, 802 Rectangle2D dataArea, 803 PlotRenderingInfo info, 804 XYPlot plot, 805 ValueAxis domainAxis, 806 ValueAxis rangeAxis, 807 XYDataset dataset, 808 int series, 809 int item, 810 CrosshairState crosshairState, 811 int pass) { 812 813 boolean itemVisible = getItemVisible(series, item); 814 815 // setup for collecting optional entity info... 816 Shape entityArea = null; 817 EntityCollection entities = null; 818 if (info != null) { 819 entities = info.getOwner().getEntityCollection(); 820 } 821 822 PlotOrientation orientation = plot.getOrientation(); 823 Paint paint = getItemPaint(series, item); 824 Stroke seriesStroke = getItemStroke(series, item); 825 g2.setPaint(paint); 826 g2.setStroke(seriesStroke); 827 828 // get the data point... 829 double x1 = dataset.getXValue(series, item); 830 double y1 = dataset.getYValue(series, item); 831 if (Double.isNaN(x1) || Double.isNaN(y1)) { 832 itemVisible = false; 833 } 834 835 RectangleEdge xAxisLocation = plot.getDomainAxisEdge(); 836 RectangleEdge yAxisLocation = plot.getRangeAxisEdge(); 837 double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation); 838 double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation); 839 840 if (getPlotLines()) { 841 if (this.drawSeriesLineAsPath) { 842 State s = (State) state; 843 if (s.getSeriesIndex() != series) { 844 // we are starting a new series path 845 s.seriesPath.reset(); 846 s.lastPointGood = false; 847 s.setSeriesIndex(series); 848 } 849 850 // update path to reflect latest point 851 if (itemVisible && !Double.isNaN(transX1) 852 && !Double.isNaN(transY1)) { 853 float x = (float) transX1; 854 float y = (float) transY1; 855 if (orientation == PlotOrientation.HORIZONTAL) { 856 x = (float) transY1; 857 y = (float) transX1; 858 } 859 if (s.isLastPointGood()) { 860 // TODO: check threshold 861 s.seriesPath.lineTo(x, y); 862 } 863 else { 864 s.seriesPath.moveTo(x, y); 865 } 866 s.setLastPointGood(true); 867 } 868 else { 869 s.setLastPointGood(false); 870 } 871 if (item == dataset.getItemCount(series) - 1) { 872 if (s.seriesIndex == series) { 873 // draw path 874 g2.setStroke(lookupSeriesStroke(series)); 875 g2.setPaint(lookupSeriesPaint(series)); 876 g2.draw(s.seriesPath); 877 } 878 } 879 } 880 881 else if (item != 0 && itemVisible) { 882 // get the previous data point... 883 double x0 = dataset.getXValue(series, item - 1); 884 double y0 = dataset.getYValue(series, item - 1); 885 if (!Double.isNaN(x0) && !Double.isNaN(y0)) { 886 boolean drawLine = true; 887 if (getPlotDiscontinuous()) { 888 // only draw a line if the gap between the current and 889 // previous data point is within the threshold 890 int numX = dataset.getItemCount(series); 891 double minX = dataset.getXValue(series, 0); 892 double maxX = dataset.getXValue(series, numX - 1); 893 if (this.gapThresholdType == UnitType.ABSOLUTE) { 894 drawLine = Math.abs(x1 - x0) <= this.gapThreshold; 895 } 896 else { 897 drawLine = Math.abs(x1 - x0) <= ((maxX - minX) 898 / numX * getGapThreshold()); 899 } 900 } 901 if (drawLine) { 902 double transX0 = domainAxis.valueToJava2D(x0, dataArea, 903 xAxisLocation); 904 double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 905 yAxisLocation); 906 907 // only draw if we have good values 908 if (Double.isNaN(transX0) || Double.isNaN(transY0) 909 || Double.isNaN(transX1) || Double.isNaN(transY1)) { 910 return; 911 } 912 913 if (orientation == PlotOrientation.HORIZONTAL) { 914 state.workingLine.setLine(transY0, transX0, 915 transY1, transX1); 916 } 917 else if (orientation == PlotOrientation.VERTICAL) { 918 state.workingLine.setLine(transX0, transY0, 919 transX1, transY1); 920 } 921 922 if (state.workingLine.intersects(dataArea)) { 923 g2.draw(state.workingLine); 924 } 925 } 926 } 927 } 928 } 929 930 // we needed to get this far even for invisible items, to ensure that 931 // seriesPath updates happened, but now there is nothing more we need 932 // to do for non-visible items... 933 if (!itemVisible) { 934 return; 935 } 936 937 if (getBaseShapesVisible()) { 938 939 Shape shape = getItemShape(series, item); 940 if (orientation == PlotOrientation.HORIZONTAL) { 941 shape = ShapeUtilities.createTranslatedShape(shape, transY1, 942 transX1); 943 } 944 else if (orientation == PlotOrientation.VERTICAL) { 945 shape = ShapeUtilities.createTranslatedShape(shape, transX1, 946 transY1); 947 } 948 if (shape.intersects(dataArea)) { 949 if (getItemShapeFilled(series, item)) { 950 g2.fill(shape); 951 } 952 else { 953 g2.draw(shape); 954 } 955 } 956 entityArea = shape; 957 958 } 959 960 if (getPlotImages()) { 961 Image image = getImage(plot, series, item, transX1, transY1); 962 if (image != null) { 963 Point hotspot = getImageHotspot(plot, series, item, transX1, 964 transY1, image); 965 g2.drawImage(image, (int) (transX1 - hotspot.getX()), 966 (int) (transY1 - hotspot.getY()), null); 967 entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(), 968 transY1 - hotspot.getY(), image.getWidth(null), 969 image.getHeight(null)); 970 } 971 972 } 973 974 double xx = transX1; 975 double yy = transY1; 976 if (orientation == PlotOrientation.HORIZONTAL) { 977 xx = transY1; 978 yy = transX1; 979 } 980 981 // draw the item label if there is one... 982 if (isItemLabelVisible(series, item)) { 983 drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 984 (y1 < 0.0)); 985 } 986 987 int domainAxisIndex = plot.getDomainAxisIndex(domainAxis); 988 int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis); 989 updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 990 rangeAxisIndex, transX1, transY1, orientation); 991 992 // add an entity for the item... 993 if (entities != null && isPointInRect(dataArea, xx, yy)) { 994 addEntity(entities, entityArea, dataset, series, item, xx, yy); 995 } 996 997 } 998 999 /** 1000 * Tests this renderer for equality with another object. 1001 * 1002 * @param obj the object (<code>null</code> permitted). 1003 * 1004 * @return A boolean. 1005 */ 1006 public boolean equals(Object obj) { 1007 1008 if (obj == this) { 1009 return true; 1010 } 1011 if (!(obj instanceof StandardXYItemRenderer)) { 1012 return false; 1013 } 1014 StandardXYItemRenderer that = (StandardXYItemRenderer) obj; 1015 if (this.baseShapesVisible != that.baseShapesVisible) { 1016 return false; 1017 } 1018 if (this.plotLines != that.plotLines) { 1019 return false; 1020 } 1021 if (this.plotImages != that.plotImages) { 1022 return false; 1023 } 1024 if (this.plotDiscontinuous != that.plotDiscontinuous) { 1025 return false; 1026 } 1027 if (this.gapThresholdType != that.gapThresholdType) { 1028 return false; 1029 } 1030 if (this.gapThreshold != that.gapThreshold) { 1031 return false; 1032 } 1033 if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) { 1034 return false; 1035 } 1036 if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) { 1037 return false; 1038 } 1039 if (this.baseShapesFilled != that.baseShapesFilled) { 1040 return false; 1041 } 1042 if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) { 1043 return false; 1044 } 1045 if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) { 1046 return false; 1047 } 1048 return super.equals(obj); 1049 1050 } 1051 1052 /** 1053 * Returns a clone of the renderer. 1054 * 1055 * @return A clone. 1056 * 1057 * @throws CloneNotSupportedException if the renderer cannot be cloned. 1058 */ 1059 public Object clone() throws CloneNotSupportedException { 1060 StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone(); 1061 clone.seriesShapesFilled 1062 = (BooleanList) this.seriesShapesFilled.clone(); 1063 clone.legendLine = ShapeUtilities.clone(this.legendLine); 1064 return clone; 1065 } 1066 1067 //////////////////////////////////////////////////////////////////////////// 1068 // PROTECTED METHODS 1069 // These provide the opportunity to subclass the standard renderer and 1070 // create custom effects. 1071 //////////////////////////////////////////////////////////////////////////// 1072 1073 /** 1074 * Returns the image used to draw a single data item. 1075 * 1076 * @param plot the plot (can be used to obtain standard color information 1077 * etc). 1078 * @param series the series index. 1079 * @param item the item index. 1080 * @param x the x value of the item. 1081 * @param y the y value of the item. 1082 * 1083 * @return The image. 1084 * 1085 * @see #getPlotImages() 1086 */ 1087 protected Image getImage(Plot plot, int series, int item, 1088 double x, double y) { 1089 // this method must be overridden if you want to display images 1090 return null; 1091 } 1092 1093 /** 1094 * Returns the hotspot of the image used to draw a single data item. 1095 * The hotspot is the point relative to the top left of the image 1096 * that should indicate the data item. The default is the center of the 1097 * image. 1098 * 1099 * @param plot the plot (can be used to obtain standard color information 1100 * etc). 1101 * @param image the image (can be used to get size information about the 1102 * image) 1103 * @param series the series index 1104 * @param item the item index 1105 * @param x the x value of the item 1106 * @param y the y value of the item 1107 * 1108 * @return The hotspot used to draw the data item. 1109 */ 1110 protected Point getImageHotspot(Plot plot, int series, int item, 1111 double x, double y, Image image) { 1112 1113 int height = image.getHeight(null); 1114 int width = image.getWidth(null); 1115 return new Point(width / 2, height / 2); 1116 1117 } 1118 1119 /** 1120 * Provides serialization support. 1121 * 1122 * @param stream the input stream. 1123 * 1124 * @throws IOException if there is an I/O error. 1125 * @throws ClassNotFoundException if there is a classpath problem. 1126 */ 1127 private void readObject(ObjectInputStream stream) 1128 throws IOException, ClassNotFoundException { 1129 stream.defaultReadObject(); 1130 this.legendLine = SerialUtilities.readShape(stream); 1131 } 1132 1133 /** 1134 * Provides serialization support. 1135 * 1136 * @param stream the output stream. 1137 * 1138 * @throws IOException if there is an I/O error. 1139 */ 1140 private void writeObject(ObjectOutputStream stream) throws IOException { 1141 stream.defaultWriteObject(); 1142 SerialUtilities.writeShape(this.legendLine, stream); 1143 } 1144 1145 }