001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2021, 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 * 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 (for Object Refinery Limited);
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}