    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}