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