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