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