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