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