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