    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 * StandardXYItemRenderer.java
    029 * ---------------------------
    030 * (C) Copyright 2001-2008, by Object Refinery Limited and Contributors.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   Mark Watson (www.markwatson.com);
    034 *                   Jonathan Nash;
    035 *                   Andreas Schneider;
    036 *                   Norbert Kiesel (for TBD Networks);
    037 *                   Christian W. Zuckschwerdt;
    038 *                   Bill Kelemen;
    039 *                   Nicolas Brodu (for Astrium and EADS Corporate Research
    040 *                   Center);
    041 *
    042 * Changes:
    043 * --------
    044 * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG);
    045 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
    046 * 21-Dec-2001 : Added working line instance to improve performance (DG);
    047 * 22-Jan-2002 : Added code to lock crosshairs to data points.  Based on code
    048 *               by Jonathan Nash (DG);
    049 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
    050 * 28-Mar-2002 : Added a property change listener mechanism so that the
    051 *               renderer no longer needs to be immutable (DG);
    052 * 02-Apr-2002 : Modified to handle null values (DG);
    053 * 09-Apr-2002 : Modified draw method to return void.  Removed the translated
    054 *               zero from the drawItem method.  Override the initialise()
    055 *               method to calculate it (DG);
    056 * 13-May-2002 : Added code from Andreas Schneider to allow changing
    057 *               shapes/colors per item (DG);
    058 * 24-May-2002 : Incorporated tooltips into chart entities (DG);
    059 * 25-Jun-2002 : Removed redundant code (DG);
    060 * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA);
    061 * 08-Aug-2002 : Added discontinuous lines option contributed by
    062 *               Norbert Kiesel (DG);
    063 * 20-Aug-2002 : Added user definable default values to be returned by
    064 *               protected methods unless overridden by a subclass (DG);
    065 * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG);
    066 * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    067 * 25-Mar-2003 : Implemented Serializable (DG);
    068 * 01-May-2003 : Modified drawItem() method signature (DG);
    069 * 15-May-2003 : Modified to take into account the plot orientation (DG);
    070 * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
    071 * 30-Jul-2003 : Modified entity constructor (CZ);
    072 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    073 * 24-Aug-2003 : Added null/NaN checks in drawItem (BK);
    074 * 08-Sep-2003 : Fixed serialization (NB);
    075 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    076 * 21-Jan-2004 : Override for getLegendItem() method (DG);
    077 * 27-Jan-2004 : Moved working line into state object (DG);
    078 * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding
    079 *               easier (DG);
    080 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    081 *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    082 * 08-Jun-2004 : Modified to use getX() and getY() methods (DG);
    083 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    084 *               getYValue() (DG);
    085 * 25-Aug-2004 : Created addEntity() method in superclass (DG);
    086 * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG);
    087 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    088 * 23-Feb-2005 : Fixed getLegendItem() method to show lines.  Fixed bug
    089 *               1077108 (shape not visible for first item in series) (DG);
    090 * 10-Apr-2005 : Fixed item label positioning with horizontal orientation (DG);
    091 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
    092 * 27-Apr-2005 : Use generator for series label in legend (DG);
    093 * ------------- JFREECHART 1.0.x ---------------------------------------------
    094 * 15-Jun-2006 : Fixed bug (1380480) for rendering series as path (DG);
    095 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    096 * 14-Mar-2007 : Fixed problems with the equals() and clone() methods (DG);
    097 * 23-Mar-2007 : Clean-up of shapesFilled attributes (DG);
    098 * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer
    099 *               change (DG);
    100 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem()
    101 *               method (DG);
    102 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    103 * 08-Jun-2007 : Fixed bug in entity creation (DG);
    104 * 21-Nov-2007 : Deprecated override flag methods (DG);
    105 * 02-Jun-2008 : Fixed tooltips for data items at lower edges of data area (DG);
    106 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
    107 *
    108 */
    109
    110package org.jfree.chart.renderer.xy;
    111
    112import java.awt.Graphics2D;
    113import java.awt.Image;
    114import java.awt.Paint;
    115import java.awt.Point;
    116import java.awt.Shape;
    117import java.awt.Stroke;
    118import java.awt.geom.GeneralPath;
    119import java.awt.geom.Line2D;
    120import java.awt.geom.Rectangle2D;
    121import java.io.IOException;
    122import java.io.ObjectInputStream;
    123import java.io.ObjectOutputStream;
    124import java.io.Serializable;
    125
    126import org.jfree.chart.LegendItem;
    127import org.jfree.chart.axis.ValueAxis;
    128import org.jfree.chart.entity.EntityCollection;
    129import org.jfree.chart.event.RendererChangeEvent;
    130import org.jfree.chart.labels.XYToolTipGenerator;
    131import org.jfree.chart.plot.CrosshairState;
    132import org.jfree.chart.plot.Plot;
    133import org.jfree.chart.plot.PlotOrientation;
    134import org.jfree.chart.plot.PlotRenderingInfo;
    135import org.jfree.chart.plot.XYPlot;
    136import org.jfree.chart.urls.XYURLGenerator;
    137import org.jfree.data.xy.XYDataset;
    138import org.jfree.io.SerialUtilities;
    139import org.jfree.ui.RectangleEdge;
    140import org.jfree.util.BooleanList;
    141import org.jfree.util.BooleanUtilities;
    142import org.jfree.util.ObjectUtilities;
    143import org.jfree.util.PublicCloneable;
    144import org.jfree.util.ShapeUtilities;
    145import org.jfree.util.UnitType;
    146
    147/**
    148 * Standard item renderer for an {@link XYPlot}.  This class can draw (a)
    149 * shapes at each point, or (b) lines between points, or (c) both shapes and
    150 * lines.
    151 * <P>
    152 * This renderer has been retained for historical reasons and, in general, you
    153 * should use the {@link XYLineAndShapeRenderer} class instead.
    154 */
    155public class StandardXYItemRenderer extends AbstractXYItemRenderer
    156        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
    157
    158    /** For serialization. */
    159    private static final long serialVersionUID = -3271351259436865995L;
    160
    161    /** Constant for the type of rendering (shapes only). */
    162    public static final int SHAPES = 1;
    163
    164    /** Constant for the type of rendering (lines only). */
    165    public static final int LINES = 2;
    166
    167    /** Constant for the type of rendering (shapes and lines). */
    168    public static final int SHAPES_AND_LINES = SHAPES | LINES;
    169
    170    /** Constant for the type of rendering (images only). */
    171    public static final int IMAGES = 4;
    172
    173    /** Constant for the type of rendering (discontinuous lines). */
    174    public static final int DISCONTINUOUS = 8;
    175
    176    /** Constant for the type of rendering (discontinuous lines). */
    177    public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS;
    178
    179    /** A flag indicating whether or not shapes are drawn at each XY point. */
    180    private boolean baseShapesVisible;
    181
    182    /** A flag indicating whether or not lines are drawn between XY points. */
    183    private boolean plotLines;
    184
    185    /** A flag indicating whether or not images are drawn between XY points. */
    186    private boolean plotImages;
    187
    188    /** A flag controlling whether or not discontinuous lines are used. */
    189    private boolean plotDiscontinuous;
    190
    191    /** Specifies how the gap threshold value is interpreted. */
    192    private UnitType gapThresholdType = UnitType.RELATIVE;
    193
    194    /** Threshold for deciding when to discontinue a line. */
    195    private double gapThreshold = 1.0;
    196
    197    /**
    198     * A flag that controls whether or not shapes are filled for ALL series.
    199     *
    200     * @deprecated As of 1.0.8, this override should not be used.
    201     */
    202    private Boolean shapesFilled;
    203
    204    /**
    205     * A table of flags that control (per series) whether or not shapes are
    206     * filled.
    207     */
    208    private BooleanList seriesShapesFilled;
    209
    210    /** The default value returned by the getShapeFilled() method. */
    211    private boolean baseShapesFilled;
    212
    213    /**
    214     * A flag that controls whether or not each series is drawn as a single
    215     * path.
    216     */
    217    private boolean drawSeriesLineAsPath;
    218
    219    /**
    220     * The shape that is used to represent a line in the legend.
    221     * This should never be set to <code>null</code>.
    222     */
    223    private transient Shape legendLine;
    224
    225    /**
    226     * Constructs a new renderer.
    227     */
    228    public StandardXYItemRenderer() {
    229        this(LINES, null);
    230    }
    231
    232    /**
    233     * Constructs a new renderer.  To specify the type of renderer, use one of
    234     * the constants: {@link #SHAPES}, {@link #LINES} or
    235     * {@link #SHAPES_AND_LINES}.
    236     *
    237     * @param type  the type.
    238     */
    239    public StandardXYItemRenderer(int type) {
    240        this(type, null);
    241    }
    242
    243    /**
    244     * Constructs a new renderer.  To specify the type of renderer, use one of
    245     * the constants: {@link #SHAPES}, {@link #LINES} or
    246     * {@link #SHAPES_AND_LINES}.
    247     *
    248     * @param type  the type of renderer.
    249     * @param toolTipGenerator  the item label generator (<code>null</code>
    250     *                          permitted).
    251     */
    252    public StandardXYItemRenderer(int type,
    253                                  XYToolTipGenerator toolTipGenerator) {
    254        this(type, toolTipGenerator, null);
    255    }
    256
    257    /**
    258     * Constructs a new renderer.  To specify the type of renderer, use one of
    259     * the constants: {@link #SHAPES}, {@link #LINES} or
    260     * {@link #SHAPES_AND_LINES}.
    261     *
    262     * @param type  the type of renderer.
    263     * @param toolTipGenerator  the item label generator (<code>null</code>
    264     *                          permitted).
    265     * @param urlGenerator  the URL generator.
    266     */
    267    public StandardXYItemRenderer(int type,
    268                                  XYToolTipGenerator toolTipGenerator,
    269                                  XYURLGenerator urlGenerator) {
    270
    271        super();
    272        setBaseToolTipGenerator(toolTipGenerator);
    273        setURLGenerator(urlGenerator);
    274        if ((type & SHAPES) != 0) {
    275            this.baseShapesVisible = true;
    276        }
    277        if ((type & LINES) != 0) {
    278            this.plotLines = true;
    279        }
    280        if ((type & IMAGES) != 0) {
    281            this.plotImages = true;
    282        }
    283        if ((type & DISCONTINUOUS) != 0) {
    284            this.plotDiscontinuous = true;
    285        }
    286
    287        this.shapesFilled = null;
    288        this.seriesShapesFilled = new BooleanList();
    289        this.baseShapesFilled = true;
    290        this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
    291        this.drawSeriesLineAsPath = false;
    292    }
    293
    294    /**
    295     * Returns true if shapes are being plotted by the renderer.
    296     *
    297     * @return <code>true</code> if shapes are being plotted by the renderer.
    298     *
    299     * @see #setBaseShapesVisible
    300     */
    301    public boolean getBaseShapesVisible() {
    302        return this.baseShapesVisible;
    303    }
    304
    305    /**
    306     * Sets the flag that controls whether or not a shape is plotted at each
    307     * data point.
    308     *
    309     * @param flag  the flag.
    310     *
    311     * @see #getBaseShapesVisible
    312     */
    313    public void setBaseShapesVisible(boolean flag) {
    314        if (this.baseShapesVisible != flag) {
    315            this.baseShapesVisible = flag;
    316            fireChangeEvent();
    317        }
    318    }
    319
    320    // SHAPES FILLED
    321
    322    /**
    323     * Returns the flag used to control whether or not the shape for an item is
    324     * filled.
    325     * <p>
    326     * The default implementation passes control to the
    327     * <code>getSeriesShapesFilled</code> method.  You can override this method
    328     * if you require different behaviour.
    329     *
    330     * @param series  the series index (zero-based).
    331     * @param item  the item index (zero-based).
    332     *
    333     * @return A boolean.
    334     *
    335     * @see #getSeriesShapesFilled(int)
    336     */
    337    public boolean getItemShapeFilled(int series, int item) {
    338        // return the overall setting, if there is one...
    339        if (this.shapesFilled != null) {
    340            return this.shapesFilled.booleanValue();
    341        }
    342
    343        // otherwise look up the paint table
    344        Boolean flag = this.seriesShapesFilled.getBoolean(series);
    345        if (flag != null) {
    346            return flag.booleanValue();
    347        }
    348        else {
    349            return this.baseShapesFilled;
    350        }
    351    }
    352
    353    /**
    354     * Returns the override flag that controls whether or not shapes are filled
    355     * for ALL series.
    356     *
    357     * @return The flag (possibly <code>null</code>).
    358     *
    359     * @since 1.0.5
    360     *
    361     * @deprecated As of 1.0.8, you should avoid using this method and rely
    362     *             on just the per-series ({@link #getSeriesShapesFilled(int)})
    363     *             and base-level ({@link #getBaseShapesFilled()}) settings.
    364     */
    365    public Boolean getShapesFilled() {
    366        return this.shapesFilled;
    367    }
    368
    369    /**
    370     * Sets the override flag that controls whether or not shapes are filled
    371     * for ALL series and sends a {@link RendererChangeEvent} to all registered
    372     * listeners.
    373     *
    374     * @param filled  the flag.
    375     *
    376     * @see #setShapesFilled(Boolean)
    377     *
    378     * @deprecated As of 1.0.8, you should avoid using this method and rely
    379     *             on just the per-series ({@link #setSeriesShapesFilled(int,
    380     *             Boolean)}) and base-level ({@link #setBaseShapesVisible(
    381     *             boolean)}) settings.
    382     */
    383    public void setShapesFilled(boolean filled) {
    384        // here we use BooleanUtilities to remain compatible with JDKs < 1.4
    385        setShapesFilled(BooleanUtilities.valueOf(filled));
    386    }
    387
    388    /**
    389     * Sets the override flag that controls whether or not shapes are filled
    390     * for ALL series and sends a {@link RendererChangeEvent} to all registered
    391     * listeners.
    392     *
    393     * @param filled  the flag (<code>null</code> permitted).
    394     *
    395     * @see #setShapesFilled(boolean)
    396     *
    397     * @deprecated As of 1.0.8, you should avoid using this method and rely
    398     *             on just the per-series ({@link #setSeriesShapesFilled(int,
    399     *             Boolean)}) and base-level ({@link #setBaseShapesVisible(
    400     *             boolean)}) settings.
    401     */
    402    public void setShapesFilled(Boolean filled) {
    403        this.shapesFilled = filled;
    404        fireChangeEvent();
    405    }
    406
    407    /**
    408     * Returns the flag used to control whether or not the shapes for a series
    409     * are filled.
    410     *
    411     * @param series  the series index (zero-based).
    412     *
    413     * @return A boolean.
    414     */
    415    public Boolean getSeriesShapesFilled(int series) {
    416        return this.seriesShapesFilled.getBoolean(series);
    417    }
    418
    419    /**
    420     * Sets the 'shapes filled' flag for a series and sends a
    421     * {@link RendererChangeEvent} to all registered listeners.
    422     *
    423     * @param series  the series index (zero-based).
    424     * @param flag  the flag.
    425     *
    426     * @see #getSeriesShapesFilled(int)
    427     */
    428    public void setSeriesShapesFilled(int series, Boolean flag) {
    429        this.seriesShapesFilled.setBoolean(series, flag);
    430        fireChangeEvent();
    431    }
    432
    433    /**
    434     * Returns the base 'shape filled' attribute.
    435     *
    436     * @return The base flag.
    437     *
    438     * @see #setBaseShapesFilled(boolean)
    439     */
    440    public boolean getBaseShapesFilled() {
    441        return this.baseShapesFilled;
    442    }
    443
    444    /**
    445     * Sets the base 'shapes filled' flag and sends a
    446     * {@link RendererChangeEvent} to all registered listeners.
    447     *
    448     * @param flag  the flag.
    449     *
    450     * @see #getBaseShapesFilled()
    451     */
    452    public void setBaseShapesFilled(boolean flag) {
    453        this.baseShapesFilled = flag;
    454    }
    455
    456    /**
    457     * Returns true if lines are being plotted by the renderer.
    458     *
    459     * @return <code>true</code> if lines are being plotted by the renderer.
    460     *
    461     * @see #setPlotLines(boolean)
    462     */
    463    public boolean getPlotLines() {
    464        return this.plotLines;
    465    }
    466
    467    /**
    468     * Sets the flag that controls whether or not a line is plotted between
    469     * each data point and sends a {@link RendererChangeEvent} to all
    470     * registered listeners.
    471     *
    472     * @param flag  the flag.
    473     *
    474     * @see #getPlotLines()
    475     */
    476    public void setPlotLines(boolean flag) {
    477        if (this.plotLines != flag) {
    478            this.plotLines = flag;
    479            fireChangeEvent();
    480        }
    481    }
    482
    483    /**
    484     * Returns the gap threshold type (relative or absolute).
    485     *
    486     * @return The type.
    487     *
    488     * @see #setGapThresholdType(UnitType)
    489     */
    490    public UnitType getGapThresholdType() {
    491        return this.gapThresholdType;
    492    }
    493
    494    /**
    495     * Sets the gap threshold type and sends a {@link RendererChangeEvent} to
    496     * all registered listeners.
    497     *
    498     * @param thresholdType  the type (<code>null</code> not permitted).
    499     *
    500     * @see #getGapThresholdType()
    501     */
    502    public void setGapThresholdType(UnitType thresholdType) {
    503        if (thresholdType == null) {
    504            throw new IllegalArgumentException(
    505                    "Null 'thresholdType' argument.");
    506        }
    507        this.gapThresholdType = thresholdType;
    508        fireChangeEvent();
    509    }
    510
    511    /**
    512     * Returns the gap threshold for discontinuous lines.
    513     *
    514     * @return The gap threshold.
    515     *
    516     * @see #setGapThreshold(double)
    517     */
    518    public double getGapThreshold() {
    519        return this.gapThreshold;
    520    }
    521
    522    /**
    523     * Sets the gap threshold for discontinuous lines and sends a
    524     * {@link RendererChangeEvent} to all registered listeners.
    525     *
    526     * @param t  the threshold.
    527     *
    528     * @see #getGapThreshold()
    529     */
    530    public void setGapThreshold(double t) {
    531        this.gapThreshold = t;
    532        fireChangeEvent();
    533    }
    534
    535    /**
    536     * Returns true if images are being plotted by the renderer.
    537     *
    538     * @return <code>true</code> if images are being plotted by the renderer.
    539     *
    540     * @see #setPlotImages(boolean)
    541     */
    542    public boolean getPlotImages() {
    543        return this.plotImages;
    544    }
    545
    546    /**
    547     * Sets the flag that controls whether or not an image is drawn at each
    548     * data point and sends a {@link RendererChangeEvent} to all registered
    549     * listeners.
    550     *
    551     * @param flag  the flag.
    552     *
    553     * @see #getPlotImages()
    554     */
    555    public void setPlotImages(boolean flag) {
    556        if (this.plotImages != flag) {
    557            this.plotImages = flag;
    558            fireChangeEvent();
    559        }
    560    }
    561
    562    /**
    563     * Returns a flag that controls whether or not the renderer shows
    564     * discontinuous lines.
    565     *
    566     * @return <code>true</code> if lines should be discontinuous.
    567     */
    568    public boolean getPlotDiscontinuous() {
    569        return this.plotDiscontinuous;
    570    }
    571
    572    /**
    573     * Sets the flag that controls whether or not the renderer shows
    574     * discontinuous lines, and sends a {@link RendererChangeEvent} to all
    575     * registered listeners.
    576     *
    577     * @param flag  the new flag value.
    578     *
    579     * @since 1.0.5
    580     */
    581    public void setPlotDiscontinuous(boolean flag) {
    582        if (this.plotDiscontinuous != flag) {
    583            this.plotDiscontinuous = flag;
    584            fireChangeEvent();
    585        }
    586    }
    587
    588    /**
    589     * Returns a flag that controls whether or not each series is drawn as a
    590     * single path.
    591     *
    592     * @return A boolean.
    593     *
    594     * @see #setDrawSeriesLineAsPath(boolean)
    595     */
    596    public boolean getDrawSeriesLineAsPath() {
    597        return this.drawSeriesLineAsPath;
    598    }
    599
    600    /**
    601     * Sets the flag that controls whether or not each series is drawn as a
    602     * single path.
    603     *
    604     * @param flag  the flag.
    605     *
    606     * @see #getDrawSeriesLineAsPath()
    607     */
    608    public void setDrawSeriesLineAsPath(boolean flag) {
    609        this.drawSeriesLineAsPath = flag;
    610    }
    611
    612    /**
    613     * Returns the shape used to represent a line in the legend.
    614     *
    615     * @return The legend line (never <code>null</code>).
    616     *
    617     * @see #setLegendLine(Shape)
    618     */
    619    public Shape getLegendLine() {
    620        return this.legendLine;
    621    }
    622
    623    /**
    624     * Sets the shape used as a line in each legend item and sends a
    625     * {@link RendererChangeEvent} to all registered listeners.
    626     *
    627     * @param line  the line (<code>null</code> not permitted).
    628     *
    629     * @see #getLegendLine()
    630     */
    631    public void setLegendLine(Shape line) {
    632        if (line == null) {
    633            throw new IllegalArgumentException("Null 'line' argument.");
    634        }
    635        this.legendLine = line;
    636        fireChangeEvent();
    637    }
    638
    639    /**
    640     * Returns a legend item for a series.
    641     *
    642     * @param datasetIndex  the dataset index (zero-based).
    643     * @param series  the series index (zero-based).
    644     *
    645     * @return A legend item for the series.
    646     */
    647    public LegendItem getLegendItem(int datasetIndex, int series) {
    648        XYPlot plot = getPlot();
    649        if (plot == null) {
    650            return null;
    651        }
    652        LegendItem result = null;
    653        XYDataset dataset = plot.getDataset(datasetIndex);
    654        if (dataset != null) {
    655            if (getItemVisible(series, 0)) {
    656                String label = getLegendItemLabelGenerator().generateLabel(
    657                        dataset, series);
    658                String description = label;
    659                String toolTipText = null;
    660                if (getLegendItemToolTipGenerator() != null) {
    661                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
    662                            dataset, series);
    663                }
    664                String urlText = null;
    665                if (getLegendItemURLGenerator() != null) {
    666                    urlText = getLegendItemURLGenerator().generateLabel(
    667                            dataset, series);
    668                }
    669                Shape shape = lookupLegendShape(series);
    670                boolean shapeFilled = getItemShapeFilled(series, 0);
    671                Paint paint = lookupSeriesPaint(series);
    672                Paint linePaint = paint;
    673                Stroke lineStroke = lookupSeriesStroke(series);
    674                result = new LegendItem(label, description, toolTipText,
    675                        urlText, this.baseShapesVisible, shape, shapeFilled,
    676                        paint, !shapeFilled, paint, lineStroke,
    677                        this.plotLines, this.legendLine, lineStroke, linePaint);
    678                result.setLabelFont(lookupLegendTextFont(series));
    679                Paint labelPaint = lookupLegendTextPaint(series);
    680                if (labelPaint != null) {
    681                    result.setLabelPaint(labelPaint);
    682                }
    683                result.setDataset(dataset);
    684                result.setDatasetIndex(datasetIndex);
    685                result.setSeriesKey(dataset.getSeriesKey(series));
    686                result.setSeriesIndex(series);
    687            }
    688        }
    689        return result;
    690    }
    691
    692    /**
    693     * Records the state for the renderer.  This is used to preserve state
    694     * information between calls to the drawItem() method for a single chart
    695     * drawing.
    696     */
    697    public static class State extends XYItemRendererState {
    698
    699        /** The path for the current series. */
    700        public GeneralPath seriesPath;
    701
    702        /** The series index. */
    703        private int seriesIndex;
    704
    705        /**
    706         * A flag that indicates if the last (x, y) point was 'good'
    707         * (non-null).
    708         */
    709        private boolean lastPointGood;
    710
    711        /**
    712         * Creates a new state instance.
    713         *
    714         * @param info  the plot rendering info.
    715         */
    716        public State(PlotRenderingInfo info) {
    717            super(info);
    718        }
    719
    720        /**
    721         * Returns a flag that indicates if the last point drawn (in the
    722         * current series) was 'good' (non-null).
    723         *
    724         * @return A boolean.
    725         */
    726        public boolean isLastPointGood() {
    727            return this.lastPointGood;
    728        }
    729
    730        /**
    731         * Sets a flag that indicates if the last point drawn (in the current
    732         * series) was 'good' (non-null).
    733         *
    734         * @param good  the flag.
    735         */
    736        public void setLastPointGood(boolean good) {
    737            this.lastPointGood = good;
    738        }
    739
    740        /**
    741         * Returns the series index for the current path.
    742         *
    743         * @return The series index for the current path.
    744         */
    745        public int getSeriesIndex() {
    746            return this.seriesIndex;
    747        }
    748
    749        /**
    750         * Sets the series index for the current path.
    751         *
    752         * @param index  the index.
    753         */
    754        public void setSeriesIndex(int index) {
    755            this.seriesIndex = index;
    756        }
    757    }
    758
    759    /**
    760     * Initialises the renderer.
    761     * <P>
    762     * This method will be called before the first item is rendered, giving the
    763     * renderer an opportunity to initialise any state information it wants to
    764     * maintain. The renderer can do nothing if it chooses.
    765     *
    766     * @param g2  the graphics device.
    767     * @param dataArea  the area inside the axes.
    768     * @param plot  the plot.
    769     * @param data  the data.
    770     * @param info  an optional info collection object to return data back to
    771     *              the caller.
    772     *
    773     * @return The renderer state.
    774     */
    775    public XYItemRendererState initialise(Graphics2D g2,
    776                                          Rectangle2D dataArea,
    777                                          XYPlot plot,
    778                                          XYDataset data,
    779                                          PlotRenderingInfo info) {
    780
    781        State state = new State(info);
    782        state.seriesPath = new GeneralPath();
    783        state.seriesIndex = -1;
    784        return state;
    785
    786    }
    787
    788    /**
    789     * Draws the visual representation of a single data item.
    790     *
    791     * @param g2  the graphics device.
    792     * @param state  the renderer state.
    793     * @param dataArea  the area within which the data is being drawn.
    794     * @param info  collects information about the drawing.
    795     * @param plot  the plot (can be used to obtain standard color information
    796     *              etc).
    797     * @param domainAxis  the domain axis.
    798     * @param rangeAxis  the range axis.
    799     * @param dataset  the dataset.
    800     * @param series  the series index (zero-based).
    801     * @param item  the item index (zero-based).
    802     * @param crosshairState  crosshair information for the plot
    803     *                        (<code>null</code> permitted).
    804     * @param pass  the pass index.
    805     */
    806    public void drawItem(Graphics2D g2,
    807                         XYItemRendererState state,
    808                         Rectangle2D dataArea,
    809                         PlotRenderingInfo info,
    810                         XYPlot plot,
    811                         ValueAxis domainAxis,
    812                         ValueAxis rangeAxis,
    813                         XYDataset dataset,
    814                         int series,
    815                         int item,
    816                         CrosshairState crosshairState,
    817                         int pass) {
    818
    819        boolean itemVisible = getItemVisible(series, item);
    820
    821        // setup for collecting optional entity info...
    822        Shape entityArea = null;
    823        EntityCollection entities = null;
    824        if (info != null) {
    825            entities = info.getOwner().getEntityCollection();
    826        }
    827
    828        PlotOrientation orientation = plot.getOrientation();
    829        Paint paint = getItemPaint(series, item);
    830        Stroke seriesStroke = getItemStroke(series, item);
    831        g2.setPaint(paint);
    832        g2.setStroke(seriesStroke);
    833
    834        // get the data point...
    835        double x1 = dataset.getXValue(series, item);
    836        double y1 = dataset.getYValue(series, item);
    837        if (Double.isNaN(x1) || Double.isNaN(y1)) {
    838            itemVisible = false;
    839        }
    840
    841        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    842        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    843        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
    844        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
    845
    846        if (getPlotLines()) {
    847            if (this.drawSeriesLineAsPath) {
    848                State s = (State) state;
    849                if (s.getSeriesIndex() != series) {
    850                    // we are starting a new series path
    851                    s.seriesPath.reset();
    852                    s.lastPointGood = false;
    853                    s.setSeriesIndex(series);
    854                }
    855
    856                // update path to reflect latest point
    857                if (itemVisible && !Double.isNaN(transX1)
    858                        && !Double.isNaN(transY1)) {
    859                    float x = (float) transX1;
    860                    float y = (float) transY1;
    861                    if (orientation == PlotOrientation.HORIZONTAL) {
    862                        x = (float) transY1;
    863                        y = (float) transX1;
    864                    }
    865                    if (s.isLastPointGood()) {
    866                        // TODO: check threshold
    867                        s.seriesPath.lineTo(x, y);
    868                    }
    869                    else {
    870                        s.seriesPath.moveTo(x, y);
    871                    }
    872                    s.setLastPointGood(true);
    873                }
    874                else {
    875                    s.setLastPointGood(false);
    876                }
    877                if (item == dataset.getItemCount(series) - 1) {
    878                    if (s.seriesIndex == series) {
    879                        // draw path
    880                        g2.setStroke(lookupSeriesStroke(series));
    881                        g2.setPaint(lookupSeriesPaint(series));
    882                        g2.draw(s.seriesPath);
    883                    }
    884                }
    885            }
    886
    887            else if (item != 0 && itemVisible) {
    888                // get the previous data point...
    889                double x0 = dataset.getXValue(series, item - 1);
    890                double y0 = dataset.getYValue(series, item - 1);
    891                if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
    892                    boolean drawLine = true;
    893                    if (getPlotDiscontinuous()) {
    894                        // only draw a line if the gap between the current and
    895                        // previous data point is within the threshold
    896                        int numX = dataset.getItemCount(series);
    897                        double minX = dataset.getXValue(series, 0);
    898                        double maxX = dataset.getXValue(series, numX - 1);
    899                        if (this.gapThresholdType == UnitType.ABSOLUTE) {
    900                            drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
    901                        }
    902                        else {
    903                            drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
    904                                / numX * getGapThreshold());
    905                        }
    906                    }
    907                    if (drawLine) {
    908                        double transX0 = domainAxis.valueToJava2D(x0, dataArea,
    909                                xAxisLocation);
    910                        double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
    911                                yAxisLocation);
    912
    913                        // only draw if we have good values
    914                        if (Double.isNaN(transX0) || Double.isNaN(transY0)
    915                            || Double.isNaN(transX1) || Double.isNaN(transY1)) {
    916                            return;
    917                        }
    918
    919                        if (orientation == PlotOrientation.HORIZONTAL) {
    920                            state.workingLine.setLine(transY0, transX0,
    921                                    transY1, transX1);
    922                        }
    923                        else if (orientation == PlotOrientation.VERTICAL) {
    924                            state.workingLine.setLine(transX0, transY0,
    925                                    transX1, transY1);
    926                        }
    927
    928                        if (state.workingLine.intersects(dataArea)) {
    929                            g2.draw(state.workingLine);
    930                        }
    931                    }
    932                }
    933            }
    934        }
    935
    936        // we needed to get this far even for invisible items, to ensure that
    937        // seriesPath updates happened, but now there is nothing more we need
    938        // to do for non-visible items...
    939        if (!itemVisible) {
    940            return;
    941        }
    942
    943        if (getBaseShapesVisible()) {
    944
    945            Shape shape = getItemShape(series, item);
    946            if (orientation == PlotOrientation.HORIZONTAL) {
    947                shape = ShapeUtilities.createTranslatedShape(shape, transY1,
    948                        transX1);
    949            }
    950            else if (orientation == PlotOrientation.VERTICAL) {
    951                shape = ShapeUtilities.createTranslatedShape(shape, transX1,
    952                        transY1);
    953            }
    954            if (shape.intersects(dataArea)) {
    955                if (getItemShapeFilled(series, item)) {
    956                    g2.fill(shape);
    957                }
    958                else {
    959                    g2.draw(shape);
    960                }
    961            }
    962            entityArea = shape;
    963
    964        }
    965
    966        if (getPlotImages()) {
    967            Image image = getImage(plot, series, item, transX1, transY1);
    968            if (image != null) {
    969                Point hotspot = getImageHotspot(plot, series, item, transX1,
    970                        transY1, image);
    971                g2.drawImage(image, (int) (transX1 - hotspot.getX()),
    972                        (int) (transY1 - hotspot.getY()), null);
    973                entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(),
    974                        transY1 - hotspot.getY(), image.getWidth(null),
    975                        image.getHeight(null));
    976            }
    977
    978        }
    979
    980        double xx = transX1;
    981        double yy = transY1;
    982        if (orientation == PlotOrientation.HORIZONTAL) {
    983            xx = transY1;
    984            yy = transX1;
    985        }
    986
    987        // draw the item label if there is one...
    988        if (isItemLabelVisible(series, item)) {
    989            drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
    990                    (y1 < 0.0));
    991        }
    992
    993        int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    994        int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    995        updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
    996                rangeAxisIndex, transX1, transY1, orientation);
    997
    998        // add an entity for the item...
    999        if (entities != null && isPointInRect(dataArea, xx, yy)) {
    1000            addEntity(entities, entityArea, dataset, series, item, xx, yy);
    1001        }
    1002
    1003    }
    1004
    1005    /**
    1006     * Tests this renderer for equality with another object.
    1007     *
    1008     * @param obj  the object (<code>null</code> permitted).
    1009     *
    1010     * @return A boolean.
    1011     */
    1012    public boolean equals(Object obj) {
    1013
    1014        if (obj == this) {
    1015            return true;
    1016        }
    1017        if (!(obj instanceof StandardXYItemRenderer)) {
    1018            return false;
    1019        }
    1020        StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
    1021        if (this.baseShapesVisible != that.baseShapesVisible) {
    1022            return false;
    1023        }
    1024        if (this.plotLines != that.plotLines) {
    1025            return false;
    1026        }
    1027        if (this.plotImages != that.plotImages) {
    1028            return false;
    1029        }
    1030        if (this.plotDiscontinuous != that.plotDiscontinuous) {
    1031            return false;
    1032        }
    1033        if (this.gapThresholdType != that.gapThresholdType) {
    1034            return false;
    1035        }
    1036        if (this.gapThreshold != that.gapThreshold) {
    1037            return false;
    1038        }
    1039        if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
    1040            return false;
    1041        }
    1042        if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) {
    1043            return false;
    1044        }
    1045        if (this.baseShapesFilled != that.baseShapesFilled) {
    1046            return false;
    1047        }
    1048        if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
    1049            return false;
    1050        }
    1051        if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
    1052            return false;
    1053        }
    1054        return super.equals(obj);
    1055
    1056    }
    1057
    1058    /**
    1059     * Returns a clone of the renderer.
    1060     *
    1061     * @return A clone.
    1062     *
    1063     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    1064     */
    1065    public Object clone() throws CloneNotSupportedException {
    1066        StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone();
    1067        clone.seriesShapesFilled
    1068                = (BooleanList) this.seriesShapesFilled.clone();
    1069        clone.legendLine = ShapeUtilities.clone(this.legendLine);
    1070        return clone;
    1071    }
    1072
    1073    ////////////////////////////////////////////////////////////////////////////
    1074    // PROTECTED METHODS
    1075    // These provide the opportunity to subclass the standard renderer and
    1076    // create custom effects.
    1077    ////////////////////////////////////////////////////////////////////////////
    1078
    1079    /**
    1080     * Returns the image used to draw a single data item.
    1081     *
    1082     * @param plot  the plot (can be used to obtain standard color information
    1083     *              etc).
    1084     * @param series  the series index.
    1085     * @param item  the item index.
    1086     * @param x  the x value of the item.
    1087     * @param y  the y value of the item.
    1088     *
    1089     * @return The image.
    1090     *
    1091     * @see #getPlotImages()
    1092     */
    1093    protected Image getImage(Plot plot, int series, int item,
    1094                             double x, double y) {
    1095        // this method must be overridden if you want to display images
    1096        return null;
    1097    }
    1098
    1099    /**
    1100     * Returns the hotspot of the image used to draw a single data item.
    1101     * The hotspot is the point relative to the top left of the image
    1102     * that should indicate the data item. The default is the center of the
    1103     * image.
    1104     *
    1105     * @param plot  the plot (can be used to obtain standard color information
    1106     *              etc).
    1107     * @param image  the image (can be used to get size information about the
    1108     *               image)
    1109     * @param series  the series index
    1110     * @param item  the item index
    1111     * @param x  the x value of the item
    1112     * @param y  the y value of the item
    1113     *
    1114     * @return The hotspot used to draw the data item.
    1115     */
    1116    protected Point getImageHotspot(Plot plot, int series, int item,
    1117                                    double x, double y, Image image) {
    1118
    1119        int height = image.getHeight(null);
    1120        int width = image.getWidth(null);
    1121        return new Point(width / 2, height / 2);
    1122
    1123    }
    1124
    1125    /**
    1126     * Provides serialization support.
    1127     *
    1128     * @param stream  the input stream.
    1129     *
    1130     * @throws IOException  if there is an I/O error.
    1131     * @throws ClassNotFoundException  if there is a classpath problem.
    1132     */
    1133    private void readObject(ObjectInputStream stream)
    1134            throws IOException, ClassNotFoundException {
    1135        stream.defaultReadObject();
    1136        this.legendLine = SerialUtilities.readShape(stream);
    1137    }
    1138
    1139    /**
    1140     * Provides serialization support.
    1141     *
    1142     * @param stream  the output stream.
    1143     *
    1144     * @throws IOException  if there is an I/O error.
    1145     */
    1146    private void writeObject(ObjectOutputStream stream) throws IOException {
    1147        stream.defaultWriteObject();
    1148        SerialUtilities.writeShape(this.legendLine, stream);
    1149    }
    1150
    1151}