    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
    060    package org.jfree.chart.renderer;
    061
    062    import java.awt.AlphaComposite;
    063    import java.awt.Composite;
    064    import java.awt.Graphics2D;
    065    import java.awt.Paint;
    066    import java.awt.Point;
    067    import java.awt.Shape;
    068    import java.awt.Stroke;
    069    import java.awt.geom.Ellipse2D;
    070    import java.awt.geom.GeneralPath;
    071    import java.awt.geom.Line2D;
    072    import java.awt.geom.PathIterator;
    073    import java.awt.geom.Rectangle2D;
    074    import java.io.IOException;
    075    import java.io.ObjectInputStream;
    076    import java.io.ObjectOutputStream;
    077    import java.util.Iterator;
    078    import java.util.List;
    079
    080    import org.jfree.chart.LegendItem;
    081    import org.jfree.chart.axis.NumberTick;
    082    import org.jfree.chart.axis.ValueAxis;
    083    import org.jfree.chart.entity.EntityCollection;
    084    import org.jfree.chart.entity.XYItemEntity;
    085    import org.jfree.chart.event.RendererChangeEvent;
    086    import org.jfree.chart.labels.XYSeriesLabelGenerator;
    087    import org.jfree.chart.labels.XYToolTipGenerator;
    088    import org.jfree.chart.plot.DrawingSupplier;
    089    import org.jfree.chart.plot.PlotOrientation;
    090    import org.jfree.chart.plot.PlotRenderingInfo;
    091    import org.jfree.chart.plot.PolarPlot;
    092    import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
    093    import org.jfree.chart.urls.XYURLGenerator;
    094    import org.jfree.data.xy.XYDataset;
    095    import org.jfree.io.SerialUtilities;
    096    import org.jfree.text.TextUtilities;
    097    import org.jfree.util.BooleanList;
    098    import org.jfree.util.BooleanUtilities;
    099    import org.jfree.util.ObjectList;
    100    import org.jfree.util.ObjectUtilities;
    101    import org.jfree.util.PublicCloneable;
    102    import org.jfree.util.ShapeUtilities;
    103
    104    /**
    105     * A renderer that can be used with the {@link PolarPlot} class.
    106     */
    107    public 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    }