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