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