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