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