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