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 * DefaultPolarItemRenderer.java 029 * ----------------------------- 030 * (C) Copyright 2004-2021, by Solution Engineering, Inc. and 031 * Contributors. 032 * 033 * Original Author: Daniel Bridenbecker, Solution Engineering, Inc.; 034 * Contributor(s): David Gilbert; 035 * Martin Hoeller (patch 2850344); 036 * 037 */ 038 039package org.jfree.chart.renderer; 040 041import java.awt.AlphaComposite; 042import java.awt.Composite; 043import java.awt.Graphics2D; 044import java.awt.Paint; 045import java.awt.Point; 046import java.awt.Shape; 047import java.awt.Stroke; 048import java.awt.geom.Ellipse2D; 049import java.awt.geom.GeneralPath; 050import java.awt.geom.Line2D; 051import java.awt.geom.PathIterator; 052import java.awt.geom.Rectangle2D; 053import java.io.IOException; 054import java.io.ObjectInputStream; 055import java.io.ObjectOutputStream; 056import java.util.HashMap; 057import java.util.List; 058import java.util.Map; 059import java.util.Objects; 060 061import org.jfree.chart.legend.LegendItem; 062import org.jfree.chart.axis.NumberTick; 063import org.jfree.chart.axis.ValueAxis; 064import org.jfree.chart.entity.EntityCollection; 065import org.jfree.chart.entity.XYItemEntity; 066import org.jfree.chart.event.RendererChangeEvent; 067import org.jfree.chart.labels.XYSeriesLabelGenerator; 068import org.jfree.chart.labels.XYToolTipGenerator; 069import org.jfree.chart.plot.DrawingSupplier; 070import org.jfree.chart.plot.PlotOrientation; 071import org.jfree.chart.plot.PlotRenderingInfo; 072import org.jfree.chart.plot.PolarPlot; 073import org.jfree.chart.text.TextUtils; 074import org.jfree.chart.urls.XYURLGenerator; 075import org.jfree.chart.internal.CloneUtils; 076import org.jfree.chart.internal.Args; 077import org.jfree.chart.api.PublicCloneable; 078import org.jfree.chart.internal.SerialUtils; 079import org.jfree.chart.internal.ShapeUtils; 080import org.jfree.data.xy.XYDataset; 081 082/** 083 * A renderer that can be used with the {@link PolarPlot} class. 084 */ 085public class DefaultPolarItemRenderer extends AbstractRenderer 086 implements PolarItemRenderer { 087 088 /** The plot that the renderer is assigned to. */ 089 private PolarPlot plot; 090 091 /** Flags that control whether the renderer fills each series or not. */ 092 private Map<Integer, Boolean> seriesFilledMap; 093 094 /** 095 * Flag that controls whether an outline is drawn for filled series or 096 * not. 097 */ 098 private boolean drawOutlineWhenFilled; 099 100 /** 101 * The composite to use when filling series. 102 */ 103 private transient Composite fillComposite; 104 105 /** 106 * A flag that controls whether the fill paint is used for filling 107 * shapes. 108 */ 109 private boolean useFillPaint; 110 111 /** 112 * The shape that is used to represent a line in the legend. 113 */ 114 private transient Shape legendLine; 115 116 /** 117 * Flag that controls whether item shapes are visible or not. 118 */ 119 private boolean shapesVisible; 120 121 /** 122 * Flag that controls if the first and last point of the dataset should be 123 * connected or not. 124 */ 125 private boolean connectFirstAndLastPoint; 126 127 /** 128 * A list of tool tip generators (one per series). 129 */ 130 private Map<Integer, XYToolTipGenerator> toolTipGeneratorMap; 131 132 /** The default tool tip generator. */ 133 private XYToolTipGenerator defaultToolTipGenerator; 134 135 /** The URL text generator. */ 136 private XYURLGenerator urlGenerator; 137 138 /** 139 * The legend item tool tip generator. 140 */ 141 private XYSeriesLabelGenerator legendItemToolTipGenerator; 142 143 /** 144 * The legend item URL generator. 145 */ 146 private XYSeriesLabelGenerator legendItemURLGenerator; 147 148 /** 149 * Creates a new instance of DefaultPolarItemRenderer 150 */ 151 public DefaultPolarItemRenderer() { 152 this.seriesFilledMap = new HashMap<>(); 153 this.drawOutlineWhenFilled = true; 154 this.fillComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); 155 this.useFillPaint = false; // use item paint for fills by default 156 this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0); 157 this.shapesVisible = true; 158 this.connectFirstAndLastPoint = true; 159 160 this.toolTipGeneratorMap = new HashMap<>(); 161 this.urlGenerator = null; 162 this.legendItemToolTipGenerator = null; 163 this.legendItemURLGenerator = null; 164 } 165 166 /** 167 * Set the plot associated with this renderer. 168 * 169 * @param plot the plot. 170 * 171 * @see #getPlot() 172 */ 173 @Override 174 public void setPlot(PolarPlot plot) { 175 this.plot = plot; 176 } 177 178 /** 179 * Return the plot associated with this renderer. 180 * 181 * @return The plot. 182 * 183 * @see #setPlot(PolarPlot) 184 */ 185 @Override 186 public PolarPlot getPlot() { 187 return this.plot; 188 } 189 190 /** 191 * Returns {@code true} if the renderer will draw an outline around 192 * a filled polygon, {@code false} otherwise. 193 * 194 * @return A boolean. 195 */ 196 public boolean getDrawOutlineWhenFilled() { 197 return this.drawOutlineWhenFilled; 198 } 199 200 /** 201 * Set the flag that controls whether the outline around a filled 202 * polygon will be drawn or not and sends a {@link RendererChangeEvent} 203 * to all registered listeners. 204 * 205 * @param drawOutlineWhenFilled the flag. 206 */ 207 public void setDrawOutlineWhenFilled(boolean drawOutlineWhenFilled) { 208 this.drawOutlineWhenFilled = drawOutlineWhenFilled; 209 fireChangeEvent(); 210 } 211 212 /** 213 * Get the composite that is used for filling. 214 * 215 * @return The composite (never {@code null}). 216 */ 217 public Composite getFillComposite() { 218 return this.fillComposite; 219 } 220 221 /** 222 * Sets the composite which will be used for filling polygons and sends a 223 * {@link RendererChangeEvent} to all registered listeners. 224 * 225 * @param composite the composite to use ({@code null} not permitted). 226 */ 227 public void setFillComposite(Composite composite) { 228 Args.nullNotPermitted(composite, "composite"); 229 this.fillComposite = composite; 230 fireChangeEvent(); 231 } 232 233 /** 234 * Returns {@code true} if a shape will be drawn for every item, or 235 * {@code false} if not. 236 * 237 * @return A boolean. 238 */ 239 public boolean getShapesVisible() { 240 return this.shapesVisible; 241 } 242 243 /** 244 * Set the flag that controls whether a shape will be drawn for every 245 * item, or not and sends a {@link RendererChangeEvent} to all registered 246 * listeners. 247 * 248 * @param visible the flag. 249 */ 250 public void setShapesVisible(boolean visible) { 251 this.shapesVisible = visible; 252 fireChangeEvent(); 253 } 254 255 /** 256 * Returns {@code true} if first and last point of a series will be 257 * connected, {@code false} otherwise. 258 * 259 * @return The current status of the flag. 260 */ 261 public boolean getConnectFirstAndLastPoint() { 262 return this.connectFirstAndLastPoint; 263 } 264 265 /** 266 * Set the flag that controls whether the first and last point of a series 267 * will be connected or not and sends a {@link RendererChangeEvent} to all 268 * registered listeners. 269 * 270 * @param connect the flag. 271 */ 272 public void setConnectFirstAndLastPoint(boolean connect) { 273 this.connectFirstAndLastPoint = connect; 274 fireChangeEvent(); 275 } 276 277 /** 278 * Returns the drawing supplier from the plot. 279 * 280 * @return The drawing supplier. 281 */ 282 @Override 283 public DrawingSupplier getDrawingSupplier() { 284 DrawingSupplier result = null; 285 PolarPlot p = getPlot(); 286 if (p != null) { 287 result = p.getDrawingSupplier(); 288 } 289 return result; 290 } 291 292 /** 293 * Returns {@code true} if the renderer should fill the specified 294 * series, and {@code false} otherwise. 295 * 296 * @param series the series index (zero-based). 297 * 298 * @return A boolean. 299 */ 300 public boolean isSeriesFilled(int series) { 301 boolean result = false; 302 Boolean b = this.seriesFilledMap.get(series); 303 if (b != null) { 304 result = b; 305 } 306 return result; 307 } 308 309 /** 310 * Sets a flag that controls whether or not a series is filled. 311 * 312 * @param series the series index. 313 * @param filled the flag. 314 */ 315 public void setSeriesFilled(int series, boolean filled) { 316 this.seriesFilledMap.put(series, filled); 317 } 318 319 /** 320 * Returns {@code true} if the renderer should use the fill paint 321 * setting to fill shapes, and {@code false} if it should just 322 * use the regular paint. 323 * 324 * @return A boolean. 325 * 326 * @see #setUseFillPaint(boolean) 327 */ 328 public boolean getUseFillPaint() { 329 return this.useFillPaint; 330 } 331 332 /** 333 * Sets the flag that controls whether the fill paint is used to fill 334 * shapes, and sends a {@link RendererChangeEvent} to all 335 * registered listeners. 336 * 337 * @param flag the flag. 338 * 339 * @see #getUseFillPaint() 340 */ 341 public void setUseFillPaint(boolean flag) { 342 this.useFillPaint = flag; 343 fireChangeEvent(); 344 } 345 346 /** 347 * Returns the shape used to represent a line in the legend. 348 * 349 * @return The legend line (never {@code null}). 350 * 351 * @see #setLegendLine(Shape) 352 */ 353 public Shape getLegendLine() { 354 return this.legendLine; 355 } 356 357 /** 358 * Sets the shape used as a line in each legend item and sends a 359 * {@link RendererChangeEvent} to all registered listeners. 360 * 361 * @param line the line ({@code null} not permitted). 362 * 363 * @see #getLegendLine() 364 */ 365 public void setLegendLine(Shape line) { 366 Args.nullNotPermitted(line, "line"); 367 this.legendLine = line; 368 fireChangeEvent(); 369 } 370 371 /** 372 * Adds an entity to the collection. 373 * 374 * @param entities the entity collection being populated. 375 * @param area the entity area (if {@code null} a default will be 376 * used). 377 * @param dataset the dataset. 378 * @param series the series. 379 * @param item the item. 380 * @param entityX the entity's center x-coordinate in user space (only 381 * used if {@code area} is {@code null}). 382 * @param entityY the entity's center y-coordinate in user space (only 383 * used if {@code area} is {@code null}). 384 */ 385 protected void addEntity(EntityCollection entities, Shape area, 386 XYDataset dataset, int series, int item, 387 double entityX, double entityY) { 388 if (!getItemCreateEntity(series, item)) { 389 return; 390 } 391 Shape hotspot = area; 392 if (hotspot == null) { 393 double r = getDefaultEntityRadius(); 394 double w = r * 2; 395 if (getPlot().getOrientation() == PlotOrientation.VERTICAL) { 396 hotspot = new Ellipse2D.Double(entityX - r, entityY - r, w, w); 397 } 398 else { 399 hotspot = new Ellipse2D.Double(entityY - r, entityX - r, w, w); 400 } 401 } 402 String tip = null; 403 XYToolTipGenerator generator = getToolTipGenerator(series, item); 404 if (generator != null) { 405 tip = generator.generateToolTip(dataset, series, item); 406 } 407 String url = null; 408 if (getURLGenerator() != null) { 409 url = getURLGenerator().generateURL(dataset, series, item); 410 } 411 XYItemEntity entity = new XYItemEntity(hotspot, dataset, series, item, 412 tip, url); 413 entities.add(entity); 414 } 415 416 /** 417 * Plots the data for a given series. 418 * 419 * @param g2 the drawing surface. 420 * @param dataArea the data area. 421 * @param info collects plot rendering info. 422 * @param plot the plot. 423 * @param dataset the dataset. 424 * @param seriesIndex the series index. 425 */ 426 @Override 427 public void drawSeries(Graphics2D g2, Rectangle2D dataArea, 428 PlotRenderingInfo info, PolarPlot plot, XYDataset dataset, 429 int seriesIndex) { 430 431 final int numPoints = dataset.getItemCount(seriesIndex); 432 if (numPoints == 0) { 433 return; 434 } 435 GeneralPath poly = null; 436 ValueAxis axis = plot.getAxisForDataset(plot.indexOf(dataset)); 437 for (int i = 0; i < numPoints; i++) { 438 double theta = dataset.getXValue(seriesIndex, i); 439 double radius = dataset.getYValue(seriesIndex, i); 440 Point p = plot.translateToJava2D(theta, radius, axis, dataArea); 441 if (poly == null) { 442 poly = new GeneralPath(); 443 poly.moveTo(p.x, p.y); 444 } 445 else { 446 poly.lineTo(p.x, p.y); 447 } 448 } 449 assert poly != null; 450 if (getConnectFirstAndLastPoint()) { 451 poly.closePath(); 452 } 453 454 g2.setPaint(lookupSeriesPaint(seriesIndex)); 455 g2.setStroke(lookupSeriesStroke(seriesIndex)); 456 if (isSeriesFilled(seriesIndex)) { 457 Composite savedComposite = g2.getComposite(); 458 g2.setComposite(this.fillComposite); 459 g2.fill(poly); 460 g2.setComposite(savedComposite); 461 if (this.drawOutlineWhenFilled) { 462 // draw the outline of the filled polygon 463 g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); 464 g2.draw(poly); 465 } 466 } 467 else { 468 // just the lines, no filling 469 g2.draw(poly); 470 } 471 472 // draw the item shapes 473 if (this.shapesVisible) { 474 // setup for collecting optional entity info... 475 EntityCollection entities = null; 476 if (info != null) { 477 entities = info.getOwner().getEntityCollection(); 478 } 479 480 PathIterator pi = poly.getPathIterator(null); 481 int i = 0; 482 while (!pi.isDone()) { 483 final float[] coords = new float[6]; 484 final int segType = pi.currentSegment(coords); 485 pi.next(); 486 if (segType != PathIterator.SEG_LINETO && 487 segType != PathIterator.SEG_MOVETO) { 488 continue; 489 } 490 final int x = Math.round(coords[0]); 491 final int y = Math.round(coords[1]); 492 final Shape shape = ShapeUtils.createTranslatedShape( 493 getItemShape(seriesIndex, i++), x, y); 494 495 Paint paint; 496 if (useFillPaint) { 497 paint = lookupSeriesFillPaint(seriesIndex); 498 } 499 else { 500 paint = lookupSeriesPaint(seriesIndex); 501 } 502 g2.setPaint(paint); 503 g2.fill(shape); 504 if (isSeriesFilled(seriesIndex) && this.drawOutlineWhenFilled) { 505 g2.setPaint(lookupSeriesOutlinePaint(seriesIndex)); 506 g2.setStroke(lookupSeriesOutlineStroke(seriesIndex)); 507 g2.draw(shape); 508 } 509 510 // add an entity for the item, but only if it falls within the 511 // data area... 512 if (entities != null && ShapeUtils.isPointInRect(dataArea, x, 513 y)) { 514 addEntity(entities, shape, dataset, seriesIndex, i-1, x, y); 515 } 516 } 517 } 518 } 519 520 /** 521 * Draw the angular gridlines - the spokes. 522 * 523 * @param g2 the drawing surface. 524 * @param plot the plot ({@code null} not permitted). 525 * @param ticks the ticks ({@code null} not permitted). 526 * @param dataArea the data area. 527 */ 528 @Override 529 public void drawAngularGridLines(Graphics2D g2, PolarPlot plot, 530 List ticks, Rectangle2D dataArea) { 531 532 g2.setFont(plot.getAngleLabelFont()); 533 g2.setStroke(plot.getAngleGridlineStroke()); 534 g2.setPaint(plot.getAngleGridlinePaint()); 535 536 ValueAxis axis = plot.getAxis(); 537 double centerValue, outerValue; 538 if (axis.isInverted()) { 539 outerValue = axis.getLowerBound(); 540 centerValue = axis.getUpperBound(); 541 } else { 542 outerValue = axis.getUpperBound(); 543 centerValue = axis.getLowerBound(); 544 } 545 Point center = plot.translateToJava2D(0, centerValue, axis, dataArea); 546 for (Object o : ticks) { 547 NumberTick tick = (NumberTick) o; 548 double tickVal = tick.getNumber().doubleValue(); 549 Point p = plot.translateToJava2D(tickVal, outerValue, axis, 550 dataArea); 551 g2.setPaint(plot.getAngleGridlinePaint()); 552 g2.drawLine(center.x, center.y, p.x, p.y); 553 if (plot.isAngleLabelsVisible()) { 554 int x = p.x; 555 int y = p.y; 556 g2.setPaint(plot.getAngleLabelPaint()); 557 TextUtils.drawAlignedString(tick.getText(), g2, x, y, 558 tick.getTextAnchor()); 559 } 560 } 561 } 562 563 /** 564 * Draw the radial gridlines - the rings. 565 * 566 * @param g2 the drawing surface ({@code null} not permitted). 567 * @param plot the plot ({@code null} not permitted). 568 * @param radialAxis the radial axis ({@code null} not permitted). 569 * @param ticks the ticks ({@code null} not permitted). 570 * @param dataArea the data area. 571 */ 572 @Override 573 public void drawRadialGridLines(Graphics2D g2, PolarPlot plot, 574 ValueAxis radialAxis, List ticks, Rectangle2D dataArea) { 575 576 Args.nullNotPermitted(radialAxis, "radialAxis"); 577 g2.setFont(radialAxis.getTickLabelFont()); 578 g2.setPaint(plot.getRadiusGridlinePaint()); 579 g2.setStroke(plot.getRadiusGridlineStroke()); 580 581 double centerValue; 582 if (radialAxis.isInverted()) { 583 centerValue = radialAxis.getUpperBound(); 584 } else { 585 centerValue = radialAxis.getLowerBound(); 586 } 587 Point center = plot.translateToJava2D(0, centerValue, radialAxis, dataArea); 588 589 for (Object o : ticks) { 590 NumberTick tick = (NumberTick) o; 591 double angleDegrees = plot.isCounterClockwise() 592 ? plot.getAngleOffset() : -plot.getAngleOffset(); 593 Point p = plot.translateToJava2D(angleDegrees, 594 tick.getNumber().doubleValue(), radialAxis, dataArea); 595 int r = p.x - center.x; 596 int upperLeftX = center.x - r; 597 int upperLeftY = center.y - r; 598 int d = 2 * r; 599 Ellipse2D ring = new Ellipse2D.Double(upperLeftX, upperLeftY, d, d); 600 g2.setPaint(plot.getRadiusGridlinePaint()); 601 g2.draw(ring); 602 } 603 } 604 605 /** 606 * Return the legend for the given series. 607 * 608 * @param series the series index. 609 * 610 * @return The legend item. 611 */ 612 @Override 613 public LegendItem getLegendItem(int series) { 614 LegendItem result; 615 PolarPlot plot = getPlot(); 616 if (plot == null) { 617 return null; 618 } 619 XYDataset dataset = plot.getDataset(plot.getIndexOf(this)); 620 if (dataset == null) { 621 return null; 622 } 623 624 String toolTipText = null; 625 if (getLegendItemToolTipGenerator() != null) { 626 toolTipText = getLegendItemToolTipGenerator().generateLabel( 627 dataset, series); 628 } 629 String urlText = null; 630 if (getLegendItemURLGenerator() != null) { 631 urlText = getLegendItemURLGenerator().generateLabel(dataset, 632 series); 633 } 634 635 Comparable seriesKey = dataset.getSeriesKey(series); 636 String label = seriesKey.toString(); 637 String description = label; 638 Shape shape = lookupSeriesShape(series); 639 Paint paint; 640 if (this.useFillPaint) { 641 paint = lookupSeriesFillPaint(series); 642 } 643 else { 644 paint = lookupSeriesPaint(series); 645 } 646 Stroke stroke = lookupSeriesStroke(series); 647 Paint outlinePaint = lookupSeriesOutlinePaint(series); 648 Stroke outlineStroke = lookupSeriesOutlineStroke(series); 649 boolean shapeOutlined = isSeriesFilled(series) 650 && this.drawOutlineWhenFilled; 651 result = new LegendItem(label, description, toolTipText, urlText, 652 getShapesVisible(), shape, /* shapeFilled=*/ true, paint, 653 shapeOutlined, outlinePaint, outlineStroke, 654 /* lineVisible= */ true, this.legendLine, stroke, paint); 655 result.setToolTipText(toolTipText); 656 result.setURLText(urlText); 657 result.setDataset(dataset); 658 result.setSeriesKey(seriesKey); 659 result.setSeriesIndex(series); 660 661 return result; 662 } 663 664 /** 665 * Returns the tooltip generator for the specified series and item. 666 * 667 * @param series the series index. 668 * @param item the item index. 669 * 670 * @return The tooltip generator (possibly {@code null}). 671 */ 672 @Override 673 public XYToolTipGenerator getToolTipGenerator(int series, int item) { 674 XYToolTipGenerator generator = this.toolTipGeneratorMap.get(series); 675 if (generator == null) { 676 generator = this.defaultToolTipGenerator; 677 } 678 return generator; 679 } 680 681 /** 682 * Returns the tool tip generator for the specified series. 683 * 684 * @return The tooltip generator (possibly {@code null}). 685 */ 686 @Override 687 public XYToolTipGenerator getSeriesToolTipGenerator(int series) { 688 return this.toolTipGeneratorMap.get(series); 689 } 690 691 /** 692 * Sets the tooltip generator for the specified series. 693 * 694 * @param series the series index. 695 * @param generator the tool tip generator ({@code null} permitted). 696 */ 697 @Override 698 public void setSeriesToolTipGenerator(int series, XYToolTipGenerator generator) { 699 this.toolTipGeneratorMap.put(series, generator); 700 fireChangeEvent(); 701 } 702 703 /** 704 * Returns the default tool tip generator. 705 * 706 * @return The default tool tip generator (possibly {@code null}). 707 */ 708 @Override 709 public XYToolTipGenerator getDefaultToolTipGenerator() { 710 return this.defaultToolTipGenerator; 711 } 712 713 /** 714 * Sets the default tool tip generator and sends a 715 * {@link RendererChangeEvent} to all registered listeners. 716 * 717 * @param generator the generator ({@code null} permitted). 718 */ 719 @Override 720 public void setDefaultToolTipGenerator(XYToolTipGenerator generator) { 721 this.defaultToolTipGenerator = generator; 722 fireChangeEvent(); 723 } 724 725 /** 726 * Returns the URL generator. 727 * 728 * @return The URL generator (possibly {@code null}). 729 */ 730 @Override 731 public XYURLGenerator getURLGenerator() { 732 return this.urlGenerator; 733 } 734 735 /** 736 * Sets the URL generator. 737 * 738 * @param urlGenerator the generator ({@code null} permitted) 739 */ 740 @Override 741 public void setURLGenerator(XYURLGenerator urlGenerator) { 742 this.urlGenerator = urlGenerator; 743 fireChangeEvent(); 744 } 745 746 /** 747 * Returns the legend item tool tip generator. 748 * 749 * @return The tool tip generator (possibly {@code null}). 750 * 751 * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator) 752 */ 753 public XYSeriesLabelGenerator getLegendItemToolTipGenerator() { 754 return this.legendItemToolTipGenerator; 755 } 756 757 /** 758 * Sets the legend item tool tip generator and sends a 759 * {@link RendererChangeEvent} to all registered listeners. 760 * 761 * @param generator the generator ({@code null} permitted). 762 * 763 * @see #getLegendItemToolTipGenerator() 764 */ 765 public void setLegendItemToolTipGenerator( 766 XYSeriesLabelGenerator generator) { 767 this.legendItemToolTipGenerator = generator; 768 fireChangeEvent(); 769 } 770 771 /** 772 * Returns the legend item URL generator. 773 * 774 * @return The URL generator (possibly {@code null}). 775 * 776 * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator) 777 */ 778 public XYSeriesLabelGenerator getLegendItemURLGenerator() { 779 return this.legendItemURLGenerator; 780 } 781 782 /** 783 * Sets the legend item URL generator and sends a 784 * {@link RendererChangeEvent} to all registered listeners. 785 * 786 * @param generator the generator ({@code null} permitted). 787 * 788 * @see #getLegendItemURLGenerator() 789 */ 790 public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) { 791 this.legendItemURLGenerator = generator; 792 fireChangeEvent(); 793 } 794 795 /** 796 * Tests this renderer for equality with an arbitrary object. 797 * 798 * @param obj the object ({@code null} not permitted). 799 * 800 * @return {@code true} if this renderer is equal to {@code obj}, 801 * and {@code false} otherwise. 802 */ 803 @Override 804 public boolean equals(Object obj) { 805 if (obj == null) { 806 return false; 807 } 808 if (!(obj instanceof DefaultPolarItemRenderer)) { 809 return false; 810 } 811 DefaultPolarItemRenderer that = (DefaultPolarItemRenderer) obj; 812 if (!this.seriesFilledMap.equals(that.seriesFilledMap)) { 813 return false; 814 } 815 if (this.drawOutlineWhenFilled != that.drawOutlineWhenFilled) { 816 return false; 817 } 818 if (!Objects.equals(this.fillComposite, that.fillComposite)) { 819 return false; 820 } 821 if (this.useFillPaint != that.useFillPaint) { 822 return false; 823 } 824 if (!ShapeUtils.equal(this.legendLine, that.legendLine)) { 825 return false; 826 } 827 if (this.shapesVisible != that.shapesVisible) { 828 return false; 829 } 830 if (this.connectFirstAndLastPoint != that.connectFirstAndLastPoint) { 831 return false; 832 } 833 if (!this.toolTipGeneratorMap.equals(that.toolTipGeneratorMap)) { 834 return false; 835 } 836 if (!Objects.equals(this.defaultToolTipGenerator, that.defaultToolTipGenerator)) { 837 return false; 838 } 839 if (!Objects.equals(this.urlGenerator, that.urlGenerator)) { 840 return false; 841 } 842 if (!Objects.equals(this.legendItemToolTipGenerator, that.legendItemToolTipGenerator)) { 843 return false; 844 } 845 if (!Objects.equals(this.legendItemURLGenerator, that.legendItemURLGenerator)) { 846 return false; 847 } 848 return super.equals(obj); 849 } 850 851 /** 852 * Returns a clone of the renderer. 853 * 854 * @return A clone. 855 * 856 * @throws CloneNotSupportedException if the renderer cannot be cloned. 857 */ 858 @Override 859 public Object clone() throws CloneNotSupportedException { 860 DefaultPolarItemRenderer clone = (DefaultPolarItemRenderer) super.clone(); 861 clone.legendLine = CloneUtils.clone(this.legendLine); 862 clone.seriesFilledMap = new HashMap<>(this.seriesFilledMap); 863 clone.toolTipGeneratorMap = CloneUtils.cloneMapValues(this.toolTipGeneratorMap); 864 if (clone.defaultToolTipGenerator instanceof PublicCloneable) { 865 clone.defaultToolTipGenerator = CloneUtils.clone(this.defaultToolTipGenerator); 866 } 867 if (clone.urlGenerator instanceof PublicCloneable) { 868 clone.urlGenerator = CloneUtils.clone(this.urlGenerator); 869 } 870 if (clone.legendItemToolTipGenerator instanceof PublicCloneable) { 871 clone.legendItemToolTipGenerator = CloneUtils.clone(this.legendItemToolTipGenerator); 872 } 873 if (clone.legendItemURLGenerator instanceof PublicCloneable) { 874 clone.legendItemURLGenerator = CloneUtils.clone(this.legendItemURLGenerator); 875 } 876// clone.defaultToolTipGenerator = CloneUtils.copy(this.defaultToolTipGenerator); 877// clone.urlGenerator = CloneUtils.copy(this.urlGenerator); 878// clone.legendItemToolTipGenerator = CloneUtils.copy(this.legendItemToolTipGenerator); 879// clone.legendItemURLGenerator = CloneUtils.copy(this.legendItemURLGenerator); 880 return clone; 881 } 882 883 /** 884 * Provides serialization support. 885 * 886 * @param stream the input stream. 887 * 888 * @throws IOException if there is an I/O error. 889 * @throws ClassNotFoundException if there is a classpath problem. 890 */ 891 private void readObject(ObjectInputStream stream) 892 throws IOException, ClassNotFoundException { 893 stream.defaultReadObject(); 894 this.legendLine = SerialUtils.readShape(stream); 895 this.fillComposite = SerialUtils.readComposite(stream); 896 } 897 898 /** 899 * Provides serialization support. 900 * 901 * @param stream the output stream. 902 * 903 * @throws IOException if there is an I/O error. 904 */ 905 private void writeObject(ObjectOutputStream stream) throws IOException { 906 stream.defaultWriteObject(); 907 SerialUtils.writeShape(this.legendLine, stream); 908 SerialUtils.writeComposite(this.fillComposite, stream); 909 } 910}