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