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