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