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