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