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 * LineAndShapeRenderer.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 * Jeremy Bowman; 035 * Richard Atkinson; 036 * Christian W. Zuckschwerdt; 037 * Peter Kolb (patch 2497611); 038 * 039 */ 040 041package org.jfree.chart.renderer.category; 042 043import java.awt.Graphics2D; 044import java.awt.Paint; 045import java.awt.Shape; 046import java.awt.Stroke; 047import java.awt.geom.Line2D; 048import java.awt.geom.Rectangle2D; 049import java.io.Serializable; 050import java.util.HashMap; 051import java.util.Map; 052import java.util.Objects; 053 054import org.jfree.chart.legend.LegendItem; 055import org.jfree.chart.axis.CategoryAxis; 056import org.jfree.chart.axis.ValueAxis; 057import org.jfree.chart.entity.EntityCollection; 058import org.jfree.chart.event.RendererChangeEvent; 059import org.jfree.chart.plot.CategoryPlot; 060import org.jfree.chart.plot.PlotOrientation; 061import org.jfree.chart.api.PublicCloneable; 062import org.jfree.chart.internal.ShapeUtils; 063import org.jfree.data.category.CategoryDataset; 064 065/** 066 * A renderer that draws shapes for each data item, and lines between data 067 * items (for use with the {@link CategoryPlot} class). 068 * The example shown here is generated by the {@code LineChartDemo1.java} 069 * program included in the JFreeChart Demo Collection: 070 * <br><br> 071 * <img src="doc-files/LineAndShapeRendererSample.png" 072 * alt="LineAndShapeRendererSample.png"> 073 */ 074public class LineAndShapeRenderer extends AbstractCategoryItemRenderer 075 implements Cloneable, PublicCloneable, Serializable { 076 077 /** For serialization. */ 078 private static final long serialVersionUID = -197749519869226398L; 079 080 /** 081 * A table of flags that control (per series) whether or not lines are 082 * visible. 083 */ 084 private Map<Integer, Boolean> seriesLinesVisibleMap; 085 086 /** 087 * A flag indicating whether or not lines are drawn between non-null 088 * points. 089 */ 090 private boolean defaultLinesVisible; 091 092 /** 093 * A table of flags that control (per series) whether or not shapes are 094 * visible. 095 */ 096 private Map<Integer, Boolean> seriesShapesVisibleMap; 097 098 /** The default value returned by the getShapeVisible() method. */ 099 private boolean defaultShapesVisible; 100 101 /** 102 * A table of flags that control (per series) whether or not shapes are 103 * filled. 104 */ 105 private Map<Integer, Boolean> seriesShapesFilledMap; 106 107 /** The default value returned by the getShapeFilled() method. */ 108 private boolean defaultShapesFilled; 109 110 /** 111 * A flag that controls whether the fill paint is used for filling 112 * shapes. 113 */ 114 private boolean useFillPaint; 115 116 /** A flag that controls whether outlines are drawn for shapes. */ 117 private boolean drawOutlines; 118 119 /** 120 * A flag that controls whether the outline paint is used for drawing shape 121 * outlines - if not, the regular series paint is used. 122 */ 123 private boolean useOutlinePaint; 124 125 /** 126 * A flag that controls whether or not the x-position for each item is 127 * offset within the category according to the series. 128 */ 129 private boolean useSeriesOffset; 130 131 /** 132 * The item margin used for series offsetting - this allows the positioning 133 * to match the bar positions of the {@link BarRenderer} class. 134 */ 135 private double itemMargin; 136 137 /** 138 * Creates a renderer with both lines and shapes visible by default. 139 */ 140 public LineAndShapeRenderer() { 141 this(true, true); 142 } 143 144 /** 145 * Creates a new renderer with lines and/or shapes visible. 146 * 147 * @param lines draw lines? 148 * @param shapes draw shapes? 149 */ 150 public LineAndShapeRenderer(boolean lines, boolean shapes) { 151 super(); 152 this.seriesLinesVisibleMap = new HashMap<>(); 153 this.defaultLinesVisible = lines; 154 this.seriesShapesVisibleMap = new HashMap<>(); 155 this.defaultShapesVisible = shapes; 156 this.seriesShapesFilledMap = new HashMap<>(); 157 this.defaultShapesFilled = true; 158 this.useFillPaint = false; 159 this.drawOutlines = true; 160 this.useOutlinePaint = false; 161 this.useSeriesOffset = false; // preserves old behaviour 162 this.itemMargin = 0.0; 163 } 164 165 // LINES VISIBLE 166 167 /** 168 * Returns the flag used to control whether or not the line for an item is 169 * visible. 170 * 171 * @param series the series index (zero-based). 172 * @param item the item index (zero-based). 173 * 174 * @return A boolean. 175 */ 176 public boolean getItemLineVisible(int series, int item) { 177 Boolean flag = getSeriesLinesVisible(series); 178 if (flag != null) { 179 return flag; 180 } 181 return this.defaultLinesVisible; 182 } 183 184 /** 185 * Returns the flag used to control whether or not the lines for a series 186 * are visible. 187 * 188 * @param series the series index (zero-based). 189 * 190 * @return The flag (possibly {@code null}). 191 * 192 * @see #setSeriesLinesVisible(int, Boolean) 193 */ 194 public Boolean getSeriesLinesVisible(int series) { 195 return this.seriesLinesVisibleMap.get(series); 196 } 197 198 /** 199 * Sets the 'lines visible' flag for a series and sends a 200 * {@link RendererChangeEvent} to all registered listeners. 201 * 202 * @param series the series index (zero-based). 203 * @param flag the flag ({@code null} permitted). 204 * 205 * @see #getSeriesLinesVisible(int) 206 */ 207 public void setSeriesLinesVisible(int series, Boolean flag) { 208 this.seriesLinesVisibleMap.put(series, flag); 209 fireChangeEvent(); 210 } 211 212 /** 213 * Sets the 'lines visible' flag for a series and sends a 214 * {@link RendererChangeEvent} to all registered listeners. 215 * 216 * @param series the series index (zero-based). 217 * @param visible the flag. 218 * 219 * @see #getSeriesLinesVisible(int) 220 */ 221 public void setSeriesLinesVisible(int series, boolean visible) { 222 setSeriesLinesVisible(series, Boolean.valueOf(visible)); 223 } 224 225 /** 226 * Returns the default 'lines visible' attribute. 227 * 228 * @return The default flag. 229 * 230 * @see #getDefaultLinesVisible() 231 */ 232 public boolean getDefaultLinesVisible() { 233 return this.defaultLinesVisible; 234 } 235 236 /** 237 * Sets the default 'lines visible' flag and sends a 238 * {@link RendererChangeEvent} to all registered listeners. 239 * 240 * @param flag the flag. 241 * 242 * @see #getDefaultLinesVisible() 243 */ 244 public void setDefaultLinesVisible(boolean flag) { 245 this.defaultLinesVisible = flag; 246 fireChangeEvent(); 247 } 248 249 // SHAPES VISIBLE 250 251 /** 252 * Returns the flag used to control whether or not the shape for an item is 253 * visible. 254 * 255 * @param series the series index (zero-based). 256 * @param item the item index (zero-based). 257 * 258 * @return A boolean. 259 */ 260 public boolean getItemShapeVisible(int series, int item) { 261 Boolean flag = getSeriesShapesVisible(series); 262 if (flag != null) { 263 return flag; 264 } 265 return this.defaultShapesVisible; 266 } 267 268 /** 269 * Returns the flag used to control whether or not the shapes for a series 270 * are visible. 271 * 272 * @param series the series index (zero-based). 273 * 274 * @return A boolean. 275 * 276 * @see #setSeriesShapesVisible(int, Boolean) 277 */ 278 public Boolean getSeriesShapesVisible(int series) { 279 return this.seriesShapesVisibleMap.get(series); 280 } 281 282 /** 283 * Sets the 'shapes visible' flag for a series and sends a 284 * {@link RendererChangeEvent} to all registered listeners. 285 * 286 * @param series the series index (zero-based). 287 * @param visible the flag. 288 * 289 * @see #getSeriesShapesVisible(int) 290 */ 291 public void setSeriesShapesVisible(int series, boolean visible) { 292 setSeriesShapesVisible(series, Boolean.valueOf(visible)); 293 } 294 295 /** 296 * Sets the 'shapes visible' flag for a series and sends a 297 * {@link RendererChangeEvent} to all registered listeners. 298 * 299 * @param series the series index (zero-based). 300 * @param flag the flag. 301 * 302 * @see #getSeriesShapesVisible(int) 303 */ 304 public void setSeriesShapesVisible(int series, Boolean flag) { 305 this.seriesShapesVisibleMap.put(series, flag); 306 fireChangeEvent(); 307 } 308 309 /** 310 * Returns the default 'shape visible' attribute. 311 * 312 * @return The base flag. 313 * 314 * @see #setDefaultShapesVisible(boolean) 315 */ 316 public boolean getDefaultShapesVisible() { 317 return this.defaultShapesVisible; 318 } 319 320 /** 321 * Sets the default 'shapes visible' flag and sends a 322 * {@link RendererChangeEvent} to all registered listeners. 323 * 324 * @param flag the flag. 325 * 326 * @see #getDefaultShapesVisible() 327 */ 328 public void setDefaultShapesVisible(boolean flag) { 329 this.defaultShapesVisible = flag; 330 fireChangeEvent(); 331 } 332 333 /** 334 * Returns {@code true} if outlines should be drawn for shapes, and 335 * {@code false} otherwise. 336 * 337 * @return A boolean. 338 * 339 * @see #setDrawOutlines(boolean) 340 */ 341 public boolean getDrawOutlines() { 342 return this.drawOutlines; 343 } 344 345 /** 346 * Sets the flag that controls whether outlines are drawn for 347 * shapes, and sends a {@link RendererChangeEvent} to all registered 348 * listeners. 349 * <P> 350 * In some cases, shapes look better if they do NOT have an outline, but 351 * this flag allows you to set your own preference. 352 * 353 * @param flag the flag. 354 * 355 * @see #getDrawOutlines() 356 */ 357 public void setDrawOutlines(boolean flag) { 358 this.drawOutlines = flag; 359 fireChangeEvent(); 360 } 361 362 /** 363 * Returns the flag that controls whether the outline paint is used for 364 * shape outlines. If not, the regular series paint is used. 365 * 366 * @return A boolean. 367 * 368 * @see #setUseOutlinePaint(boolean) 369 */ 370 public boolean getUseOutlinePaint() { 371 return this.useOutlinePaint; 372 } 373 374 /** 375 * Sets the flag that controls whether the outline paint is used for shape 376 * outlines, and sends a {@link RendererChangeEvent} to all registered 377 * listeners. 378 * 379 * @param use the flag. 380 * 381 * @see #getUseOutlinePaint() 382 */ 383 public void setUseOutlinePaint(boolean use) { 384 this.useOutlinePaint = use; 385 fireChangeEvent(); 386 } 387 388 // SHAPES FILLED 389 390 /** 391 * Returns the flag used to control whether or not the shape for an item 392 * is filled. The default implementation passes control to the 393 * {@code getSeriesShapesFilled} method. You can override this method 394 * if you require different behaviour. 395 * 396 * @param series the series index (zero-based). 397 * @param item the item index (zero-based). 398 * 399 * @return A boolean. 400 */ 401 public boolean getItemShapeFilled(int series, int item) { 402 return getSeriesShapesFilled(series); 403 } 404 405 /** 406 * Returns the flag used to control whether or not the shapes for a series 407 * are filled. 408 * 409 * @param series the series index (zero-based). 410 * 411 * @return A boolean. 412 */ 413 public boolean getSeriesShapesFilled(int series) { 414 Boolean flag = this.seriesShapesFilledMap.get(series); 415 if (flag != null) { 416 return flag; 417 } 418 return this.defaultShapesFilled; 419 } 420 421 /** 422 * Sets the 'shapes filled' flag for a series and sends a 423 * {@link RendererChangeEvent} to all registered listeners. 424 * 425 * @param series the series index (zero-based). 426 * @param filled the flag. 427 * 428 * @see #getSeriesShapesFilled(int) 429 */ 430 public void setSeriesShapesFilled(int series, Boolean filled) { 431 this.seriesShapesFilledMap.put(series, filled); 432 fireChangeEvent(); 433 } 434 435 /** 436 * Sets the 'shapes filled' flag for a series and sends a 437 * {@link RendererChangeEvent} to all registered listeners. 438 * 439 * @param series the series index (zero-based). 440 * @param filled the flag. 441 * 442 * @see #getSeriesShapesFilled(int) 443 */ 444 public void setSeriesShapesFilled(int series, boolean filled) { 445 // delegate 446 setSeriesShapesFilled(series, Boolean.valueOf(filled)); 447 } 448 449 /** 450 * Returns the default 'shape filled' attribute. 451 * 452 * @return The base flag. 453 * 454 * @see #setDefaultShapesFilled(boolean) 455 */ 456 public boolean getDefaultShapesFilled() { 457 return this.defaultShapesFilled; 458 } 459 460 /** 461 * Sets the default 'shapes filled' flag and sends a 462 * {@link RendererChangeEvent} to all registered listeners. 463 * 464 * @param flag the flag. 465 * 466 * @see #getDefaultShapesFilled() 467 */ 468 public void setDefaultShapesFilled(boolean flag) { 469 this.defaultShapesFilled = flag; 470 fireChangeEvent(); 471 } 472 473 /** 474 * Returns {@code true} if the renderer should use the fill paint 475 * setting to fill shapes, and {@code false} if it should just 476 * use the regular paint. 477 * 478 * @return A boolean. 479 * 480 * @see #setUseFillPaint(boolean) 481 */ 482 public boolean getUseFillPaint() { 483 return this.useFillPaint; 484 } 485 486 /** 487 * Sets the flag that controls whether the fill paint is used to fill 488 * shapes, and sends a {@link RendererChangeEvent} to all 489 * registered listeners. 490 * 491 * @param flag the flag. 492 * 493 * @see #getUseFillPaint() 494 */ 495 public void setUseFillPaint(boolean flag) { 496 this.useFillPaint = flag; 497 fireChangeEvent(); 498 } 499 500 /** 501 * Returns the flag that controls whether or not the x-position for each 502 * data item is offset within the category according to the series. 503 * 504 * @return A boolean. 505 * 506 * @see #setUseSeriesOffset(boolean) 507 */ 508 public boolean getUseSeriesOffset() { 509 return this.useSeriesOffset; 510 } 511 512 /** 513 * Sets the flag that controls whether or not the x-position for each 514 * data item is offset within its category according to the series, and 515 * sends a {@link RendererChangeEvent} to all registered listeners. 516 * 517 * @param offset the offset. 518 * 519 * @see #getUseSeriesOffset() 520 */ 521 public void setUseSeriesOffset(boolean offset) { 522 this.useSeriesOffset = offset; 523 fireChangeEvent(); 524 } 525 526 /** 527 * Returns the item margin, which is the gap between items within a 528 * category (expressed as a percentage of the overall category width). 529 * This can be used to match the offset alignment with the bars drawn by 530 * a {@link BarRenderer}). 531 * 532 * @return The item margin. 533 * 534 * @see #setItemMargin(double) 535 * @see #getUseSeriesOffset() 536 */ 537 public double getItemMargin() { 538 return this.itemMargin; 539 } 540 541 /** 542 * Sets the item margin, which is the gap between items within a category 543 * (expressed as a percentage of the overall category width), and sends 544 * a {@link RendererChangeEvent} to all registered listeners. 545 * 546 * @param margin the margin (0.0 <= margin < 1.0). 547 * 548 * @see #getItemMargin() 549 * @see #getUseSeriesOffset() 550 */ 551 public void setItemMargin(double margin) { 552 if (margin < 0.0 || margin >= 1.0) { 553 throw new IllegalArgumentException("Requires 0.0 <= margin < 1.0."); 554 } 555 this.itemMargin = margin; 556 fireChangeEvent(); 557 } 558 559 /** 560 * Returns a legend item for a series. 561 * 562 * @param datasetIndex the dataset index (zero-based). 563 * @param series the series index (zero-based). 564 * 565 * @return The legend item. 566 */ 567 @Override 568 public LegendItem getLegendItem(int datasetIndex, int series) { 569 570 CategoryPlot cp = getPlot(); 571 if (cp == null) { 572 return null; 573 } 574 575 if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) { 576 CategoryDataset dataset = cp.getDataset(datasetIndex); 577 String label = getLegendItemLabelGenerator().generateLabel( 578 dataset, series); 579 String description = label; 580 String toolTipText = null; 581 if (getLegendItemToolTipGenerator() != null) { 582 toolTipText = getLegendItemToolTipGenerator().generateLabel( 583 dataset, series); 584 } 585 String urlText = null; 586 if (getLegendItemURLGenerator() != null) { 587 urlText = getLegendItemURLGenerator().generateLabel( 588 dataset, series); 589 } 590 Shape shape = lookupLegendShape(series); 591 Paint paint = lookupSeriesPaint(series); 592 Paint fillPaint = (this.useFillPaint 593 ? getItemFillPaint(series, 0) : paint); 594 boolean shapeOutlineVisible = this.drawOutlines; 595 Paint outlinePaint = (this.useOutlinePaint 596 ? getItemOutlinePaint(series, 0) : paint); 597 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 598 boolean lineVisible = getItemLineVisible(series, 0); 599 boolean shapeVisible = getItemShapeVisible(series, 0); 600 LegendItem result = new LegendItem(label, description, toolTipText, 601 urlText, shapeVisible, shape, getItemShapeFilled(series, 0), 602 fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke, 603 lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0), 604 getItemStroke(series, 0), getItemPaint(series, 0)); 605 result.setLabelFont(lookupLegendTextFont(series)); 606 Paint labelPaint = lookupLegendTextPaint(series); 607 if (labelPaint != null) { 608 result.setLabelPaint(labelPaint); 609 } 610 result.setDataset(dataset); 611 result.setDatasetIndex(datasetIndex); 612 result.setSeriesKey(dataset.getRowKey(series)); 613 result.setSeriesIndex(series); 614 return result; 615 } 616 return null; 617 618 } 619 620 /** 621 * This renderer uses two passes to draw the data. 622 * 623 * @return The pass count ({@code 2} for this renderer). 624 */ 625 @Override 626 public int getPassCount() { 627 return 2; 628 } 629 630 /** 631 * Draw a single data item. 632 * 633 * @param g2 the graphics device. 634 * @param state the renderer state. 635 * @param dataArea the area in which the data is drawn. 636 * @param plot the plot. 637 * @param domainAxis the domain axis. 638 * @param rangeAxis the range axis. 639 * @param dataset the dataset. 640 * @param row the row index (zero-based). 641 * @param column the column index (zero-based). 642 * @param pass the pass index. 643 */ 644 @Override 645 public void drawItem(Graphics2D g2, CategoryItemRendererState state, 646 Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, 647 ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, 648 int pass) { 649 650 // do nothing if item is not visible 651 if (!getItemVisible(row, column)) { 652 return; 653 } 654 655 // do nothing if both the line and shape are not visible 656 if (!getItemLineVisible(row, column) 657 && !getItemShapeVisible(row, column)) { 658 return; 659 } 660 661 // nothing is drawn for null... 662 Number v = dataset.getValue(row, column); 663 if (v == null) { 664 return; 665 } 666 667 int visibleRow = state.getVisibleSeriesIndex(row); 668 if (visibleRow < 0) { 669 return; 670 } 671 int visibleRowCount = state.getVisibleSeriesCount(); 672 673 PlotOrientation orientation = plot.getOrientation(); 674 675 // current data point... 676 double x1; 677 if (this.useSeriesOffset) { 678 x1 = domainAxis.getCategorySeriesMiddle(column, 679 dataset.getColumnCount(), visibleRow, visibleRowCount, 680 this.itemMargin, dataArea, plot.getDomainAxisEdge()); 681 } 682 else { 683 x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 684 dataArea, plot.getDomainAxisEdge()); 685 } 686 double value = v.doubleValue(); 687 double y1 = rangeAxis.valueToJava2D(value, dataArea, 688 plot.getRangeAxisEdge()); 689 690 if (pass == 0 && getItemLineVisible(row, column)) { 691 if (column != 0) { 692 Number previousValue = dataset.getValue(row, column - 1); 693 if (previousValue != null) { 694 // previous data point... 695 double previous = previousValue.doubleValue(); 696 double x0; 697 if (this.useSeriesOffset) { 698 x0 = domainAxis.getCategorySeriesMiddle( 699 column - 1, dataset.getColumnCount(), 700 visibleRow, visibleRowCount, 701 this.itemMargin, dataArea, 702 plot.getDomainAxisEdge()); 703 } 704 else { 705 x0 = domainAxis.getCategoryMiddle(column - 1, 706 getColumnCount(), dataArea, 707 plot.getDomainAxisEdge()); 708 } 709 double y0 = rangeAxis.valueToJava2D(previous, dataArea, 710 plot.getRangeAxisEdge()); 711 712 Line2D line = null; 713 if (orientation == PlotOrientation.HORIZONTAL) { 714 line = new Line2D.Double(y0, x0, y1, x1); 715 } 716 else if (orientation == PlotOrientation.VERTICAL) { 717 line = new Line2D.Double(x0, y0, x1, y1); 718 } 719 g2.setPaint(getItemPaint(row, column)); 720 g2.setStroke(getItemStroke(row, column)); 721 g2.draw(line); 722 } 723 } 724 } 725 726 if (pass == 1) { 727 Shape shape = getItemShape(row, column); 728 if (orientation == PlotOrientation.HORIZONTAL) { 729 shape = ShapeUtils.createTranslatedShape(shape, y1, x1); 730 } 731 else if (orientation == PlotOrientation.VERTICAL) { 732 shape = ShapeUtils.createTranslatedShape(shape, x1, y1); 733 } 734 735 if (getItemShapeVisible(row, column)) { 736 if (getItemShapeFilled(row, column)) { 737 if (this.useFillPaint) { 738 g2.setPaint(getItemFillPaint(row, column)); 739 } 740 else { 741 g2.setPaint(getItemPaint(row, column)); 742 } 743 g2.fill(shape); 744 } 745 if (this.drawOutlines) { 746 if (this.useOutlinePaint) { 747 g2.setPaint(getItemOutlinePaint(row, column)); 748 } 749 else { 750 g2.setPaint(getItemPaint(row, column)); 751 } 752 g2.setStroke(getItemOutlineStroke(row, column)); 753 g2.draw(shape); 754 } 755 } 756 757 // draw the item label if there is one... 758 if (isItemLabelVisible(row, column)) { 759 if (orientation == PlotOrientation.HORIZONTAL) { 760 drawItemLabel(g2, orientation, dataset, row, column, y1, 761 x1, (value < 0.0)); 762 } 763 else if (orientation == PlotOrientation.VERTICAL) { 764 drawItemLabel(g2, orientation, dataset, row, column, x1, 765 y1, (value < 0.0)); 766 } 767 } 768 769 // submit the current data point as a crosshair candidate 770 int datasetIndex = plot.indexOf(dataset); 771 updateCrosshairValues(state.getCrosshairState(), 772 dataset.getRowKey(row), dataset.getColumnKey(column), 773 value, datasetIndex, x1, y1, orientation); 774 775 // add an item entity, if this information is being collected 776 EntityCollection entities = state.getEntityCollection(); 777 if (entities != null) { 778 addItemEntity(entities, dataset, row, column, shape); 779 } 780 } 781 782 } 783 784 /** 785 * Tests this renderer for equality with an arbitrary object. 786 * 787 * @param obj the object ({@code null} permitted). 788 * 789 * @return A boolean. 790 */ 791 @Override 792 public boolean equals(Object obj) { 793 794 if (obj == this) { 795 return true; 796 } 797 if (!(obj instanceof LineAndShapeRenderer)) { 798 return false; 799 } 800 801 LineAndShapeRenderer that = (LineAndShapeRenderer) obj; 802 if (this.defaultLinesVisible != that.defaultLinesVisible) { 803 return false; 804 } 805 if (!Objects.equals(this.seriesLinesVisibleMap, that.seriesLinesVisibleMap)) { 806 return false; 807 } 808 if (this.defaultShapesVisible != that.defaultShapesVisible) { 809 return false; 810 } 811 if (!Objects.equals(this.seriesShapesVisibleMap, that.seriesShapesVisibleMap)) { 812 return false; 813 } 814 if (!Objects.equals(this.seriesShapesFilledMap, that.seriesShapesFilledMap)) { 815 return false; 816 } 817 if (this.defaultShapesFilled != that.defaultShapesFilled) { 818 return false; 819 } 820 if (this.useOutlinePaint != that.useOutlinePaint) { 821 return false; 822 } 823 if (this.useSeriesOffset != that.useSeriesOffset) { 824 return false; 825 } 826 if (this.itemMargin != that.itemMargin) { 827 return false; 828 } 829 return super.equals(obj); 830 } 831 832 /** 833 * Returns an independent copy of the renderer. 834 * 835 * @return A clone. 836 * 837 * @throws CloneNotSupportedException should not happen. 838 */ 839 @Override 840 public Object clone() throws CloneNotSupportedException { 841 LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone(); 842 clone.seriesLinesVisibleMap = new HashMap<>(this.seriesLinesVisibleMap); 843 clone.seriesShapesVisibleMap = new HashMap<>(this.seriesShapesVisibleMap); 844 clone.seriesShapesFilledMap = new HashMap<>(this.seriesShapesFilledMap); 845 return clone; 846 } 847 848}