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