    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     * XYLineAndShapeRenderer.java
    029     * ---------------------------
    030     * (C) Copyright 2004-2008, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * Changes:
    036     * --------
    037     * 27-Jan-2004 : Version 1 (DG);
    038     * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste
    039     *               overriding easier (DG);
    040     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    041     * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG);
    042     * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path
    043     *               (necessary when using a dashed stroke with many data
    044     *               items) (DG);
    045     * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG);
    046     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    047     * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG);
    048     * 28-Jan-2005 : Added new constructor (DG);
    049     * 09-Mar-2005 : Added fillPaint settings (DG);
    050     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
    051     * 22-Jul-2005 : Renamed defaultLinesVisible --> baseLinesVisible,
    052     *               defaultShapesVisible --> baseShapesVisible and
    053     *               defaultShapesFilled --> baseShapesFilled (DG);
    054     * 29-Jul-2005 : Added code to draw item labels (DG);
    055     * ------------- JFREECHART 1.0.x ---------------------------------------------
    056     * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
    057     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    058     * 21-Feb-2007 : Fixed bugs in clone() and equals() (DG);
    059     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    060     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    061     * 08-Jun-2007 : Fix for bug 1731912 where entities are created even for data
    062     *               items that are not displayed (DG);
    063     * 26-Oct-2007 : Deprecated override attributes (DG);
    064     * 02-Jun-2008 : Fixed tooltips at lower edges of data area (DG);
    065     * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
    066     * 19-Sep-2008 : Fixed bug with drawSeriesLineAsPath - patch by Greg Darke (DG);
    067     *
    068     */
    069
    070    package org.jfree.chart.renderer.xy;
    071
    072    import java.awt.Graphics2D;
    073    import java.awt.Paint;
    074    import java.awt.Shape;
    075    import java.awt.Stroke;
    076    import java.awt.geom.GeneralPath;
    077    import java.awt.geom.Line2D;
    078    import java.awt.geom.Rectangle2D;
    079    import java.io.IOException;
    080    import java.io.ObjectInputStream;
    081    import java.io.ObjectOutputStream;
    082    import java.io.Serializable;
    083
    084    import org.jfree.chart.LegendItem;
    085    import org.jfree.chart.axis.ValueAxis;
    086    import org.jfree.chart.entity.EntityCollection;
    087    import org.jfree.chart.event.RendererChangeEvent;
    088    import org.jfree.chart.plot.CrosshairState;
    089    import org.jfree.chart.plot.PlotOrientation;
    090    import org.jfree.chart.plot.PlotRenderingInfo;
    091    import org.jfree.chart.plot.XYPlot;
    092    import org.jfree.data.xy.XYDataset;
    093    import org.jfree.io.SerialUtilities;
    094    import org.jfree.ui.RectangleEdge;
    095    import org.jfree.util.BooleanList;
    096    import org.jfree.util.BooleanUtilities;
    097    import org.jfree.util.ObjectUtilities;
    098    import org.jfree.util.PublicCloneable;
    099    import org.jfree.util.ShapeUtilities;
    100
    101    /**
    102     * A renderer that connects data points with lines and/or draws shapes at each
    103     * data point.  This renderer is designed for use with the {@link XYPlot}
    104     * class.  The example shown here is generated by
    105     * the <code>XYLineAndShapeRendererDemo2.java</code> program included in the
    106     * JFreeChart demo collection:
    107     * <br><br>
    108     * <img src="../../../../../images/XYLineAndShapeRendererSample.png"
    109     * alt="XYLineAndShapeRendererSample.png" />
    110     *
    111     */
    112    public class XYLineAndShapeRenderer extends AbstractXYItemRenderer
    113            implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
    114
    115        /** For serialization. */
    116        private static final long serialVersionUID = -7435246895986425885L;
    117
    118        /**
    119         * A flag that controls whether or not lines are visible for ALL series.
    120         *
    121         * @deprecated As of 1.0.7.
    122         */
    123        private Boolean linesVisible;
    124
    125        /**
    126         * A table of flags that control (per series) whether or not lines are
    127         * visible.
    128         */
    129        private BooleanList seriesLinesVisible;
    130
    131        /** The default value returned by the getLinesVisible() method. */
    132        private boolean baseLinesVisible;
    133
    134        /** The shape that is used to represent a line in the legend. */
    135        private transient Shape legendLine;
    136
    137        /**
    138         * A flag that controls whether or not shapes are visible for ALL series.
    139         *
    140         * @deprecated As of 1.0.7.
    141         */
    142        private Boolean shapesVisible;
    143
    144        /**
    145         * A table of flags that control (per series) whether or not shapes are
    146         * visible.
    147         */
    148        private BooleanList seriesShapesVisible;
    149
    150        /** The default value returned by the getShapeVisible() method. */
    151        private boolean baseShapesVisible;
    152
    153        /**
    154         * A flag that controls whether or not shapes are filled for ALL series.
    155         *
    156         * @deprecated As of 1.0.7.
    157         */
    158        private Boolean shapesFilled;
    159
    160        /**
    161         * A table of flags that control (per series) whether or not shapes are
    162         * filled.
    163         */
    164        private BooleanList seriesShapesFilled;
    165
    166        /** The default value returned by the getShapeFilled() method. */
    167        private boolean baseShapesFilled;
    168
    169        /** A flag that controls whether outlines are drawn for shapes. */
    170        private boolean drawOutlines;
    171
    172        /**
    173         * A flag that controls whether the fill paint is used for filling
    174         * shapes.
    175         */
    176        private boolean useFillPaint;
    177
    178        /**
    179         * A flag that controls whether the outline paint is used for drawing shape
    180         * outlines.
    181         */
    182        private boolean useOutlinePaint;
    183
    184        /**
    185         * A flag that controls whether or not each series is drawn as a single
    186         * path.
    187         */
    188        private boolean drawSeriesLineAsPath;
    189
    190        /**
    191         * Creates a new renderer with both lines and shapes visible.
    192         */
    193        public XYLineAndShapeRenderer() {
    194            this(true, true);
    195        }
    196
    197        /**
    198         * Creates a new renderer.
    199         *
    200         * @param lines  lines visible?
    201         * @param shapes  shapes visible?
    202         */
    203        public XYLineAndShapeRenderer(boolean lines, boolean shapes) {
    204            this.linesVisible = null;
    205            this.seriesLinesVisible = new BooleanList();
    206            this.baseLinesVisible = lines;
    207            this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
    208
    209            this.shapesVisible = null;
    210            this.seriesShapesVisible = new BooleanList();
    211            this.baseShapesVisible = shapes;
    212
    213            this.shapesFilled = null;
    214            this.useFillPaint = false;     // use item paint for fills by default
    215            this.seriesShapesFilled = new BooleanList();
    216            this.baseShapesFilled = true;
    217
    218            this.drawOutlines = true;
    219            this.useOutlinePaint = false;  // use item paint for outlines by
    220                                           // default, not outline paint
    221
    222            this.drawSeriesLineAsPath = false;
    223        }
    224
    225        /**
    226         * Returns a flag that controls whether or not each series is drawn as a
    227         * single path.
    228         *
    229         * @return A boolean.
    230         *
    231         * @see #setDrawSeriesLineAsPath(boolean)
    232         */
    233        public boolean getDrawSeriesLineAsPath() {
    234            return this.drawSeriesLineAsPath;
    235        }
    236
    237        /**
    238         * Sets the flag that controls whether or not each series is drawn as a
    239         * single path and sends a {@link RendererChangeEvent} to all registered
    240         * listeners.
    241         *
    242         * @param flag  the flag.
    243         *
    244         * @see #getDrawSeriesLineAsPath()
    245         */
    246        public void setDrawSeriesLineAsPath(boolean flag) {
    247            if (this.drawSeriesLineAsPath != flag) {
    248                this.drawSeriesLineAsPath = flag;
    249                fireChangeEvent();
    250            }
    251        }
    252
    253        /**
    254         * Returns the number of passes through the data that the renderer requires
    255         * in order to draw the chart.  Most charts will require a single pass, but
    256         * some require two passes.
    257         *
    258         * @return The pass count.
    259         */
    260        public int getPassCount() {
    261            return 2;
    262        }
    263
    264        // LINES VISIBLE
    265
    266        /**
    267         * Returns the flag used to control whether or not the shape for an item is
    268         * visible.
    269         *
    270         * @param series  the series index (zero-based).
    271         * @param item  the item index (zero-based).
    272         *
    273         * @return A boolean.
    274         */
    275        public boolean getItemLineVisible(int series, int item) {
    276            Boolean flag = this.linesVisible;
    277            if (flag == null) {
    278                flag = getSeriesLinesVisible(series);
    279            }
    280            if (flag != null) {
    281                return flag.booleanValue();
    282            }
    283            else {
    284                return this.baseLinesVisible;
    285            }
    286        }
    287
    288        /**
    289         * Returns a flag that controls whether or not lines are drawn for ALL
    290         * series.  If this flag is <code>null</code>, then the "per series"
    291         * settings will apply.
    292         *
    293         * @return A flag (possibly <code>null</code>).
    294         *
    295         * @see #setLinesVisible(Boolean)
    296         *
    297         * @deprecated As of 1.0.7, use the per-series and base level settings.
    298         */
    299        public Boolean getLinesVisible() {
    300            return this.linesVisible;
    301        }
    302
    303        /**
    304         * Sets a flag that controls whether or not lines are drawn between the
    305         * items in ALL series, and sends a {@link RendererChangeEvent} to all
    306         * registered listeners.  You need to set this to <code>null</code> if you
    307         * want the "per series" settings to apply.
    308         *
    309         * @param visible  the flag (<code>null</code> permitted).
    310         *
    311         * @see #getLinesVisible()
    312         *
    313         * @deprecated As of 1.0.7, use the per-series and base level settings.
    314         */
    315        public void setLinesVisible(Boolean visible) {
    316            this.linesVisible = visible;
    317            fireChangeEvent();
    318        }
    319
    320        /**
    321         * Sets a flag that controls whether or not lines are drawn between the
    322         * items in ALL series, and sends a {@link RendererChangeEvent} to all
    323         * registered listeners.
    324         *
    325         * @param visible  the flag.
    326         *
    327         * @see #getLinesVisible()
    328         *
    329         * @deprecated As of 1.0.7, use the per-series and base level settings.
    330         */
    331        public void setLinesVisible(boolean visible) {
    332            // we use BooleanUtilities here to preserve JRE 1.3.1 compatibility
    333            setLinesVisible(BooleanUtilities.valueOf(visible));
    334        }
    335
    336        /**
    337         * Returns the flag used to control whether or not the lines for a series
    338         * are visible.
    339         *
    340         * @param series  the series index (zero-based).
    341         *
    342         * @return The flag (possibly <code>null</code>).
    343         *
    344         * @see #setSeriesLinesVisible(int, Boolean)
    345         */
    346        public Boolean getSeriesLinesVisible(int series) {
    347            return this.seriesLinesVisible.getBoolean(series);
    348        }
    349
    350        /**
    351         * Sets the 'lines visible' flag for a series and sends a
    352         * {@link RendererChangeEvent} to all registered listeners.
    353         *
    354         * @param series  the series index (zero-based).
    355         * @param flag  the flag (<code>null</code> permitted).
    356         *
    357         * @see #getSeriesLinesVisible(int)
    358         */
    359        public void setSeriesLinesVisible(int series, Boolean flag) {
    360            this.seriesLinesVisible.setBoolean(series, flag);
    361            fireChangeEvent();
    362        }
    363
    364        /**
    365         * Sets the 'lines visible' flag for a series and sends a
    366         * {@link RendererChangeEvent} to all registered listeners.
    367         *
    368         * @param series  the series index (zero-based).
    369         * @param visible  the flag.
    370         *
    371         * @see #getSeriesLinesVisible(int)
    372         */
    373        public void setSeriesLinesVisible(int series, boolean visible) {
    374            setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
    375        }
    376
    377        /**
    378         * Returns the base 'lines visible' attribute.
    379         *
    380         * @return The base flag.
    381         *
    382         * @see #setBaseLinesVisible(boolean)
    383         */
    384        public boolean getBaseLinesVisible() {
    385            return this.baseLinesVisible;
    386        }
    387
    388        /**
    389         * Sets the base 'lines visible' flag and sends a
    390         * {@link RendererChangeEvent} to all registered listeners.
    391         *
    392         * @param flag  the flag.
    393         *
    394         * @see #getBaseLinesVisible()
    395         */
    396        public void setBaseLinesVisible(boolean flag) {
    397            this.baseLinesVisible = flag;
    398            fireChangeEvent();
    399        }
    400
    401        /**
    402         * Returns the shape used to represent a line in the legend.
    403         *
    404         * @return The legend line (never <code>null</code>).
    405         *
    406         * @see #setLegendLine(Shape)
    407         */
    408        public Shape getLegendLine() {
    409            return this.legendLine;
    410        }
    411
    412        /**
    413         * Sets the shape used as a line in each legend item and sends a
    414         * {@link RendererChangeEvent} to all registered listeners.
    415         *
    416         * @param line  the line (<code>null</code> not permitted).
    417         *
    418         * @see #getLegendLine()
    419         */
    420        public void setLegendLine(Shape line) {
    421            if (line == null) {
    422                throw new IllegalArgumentException("Null 'line' argument.");
    423            }
    424            this.legendLine = line;
    425            fireChangeEvent();
    426        }
    427
    428        // SHAPES VISIBLE
    429
    430        /**
    431         * Returns the flag used to control whether or not the shape for an item is
    432         * visible.
    433         * <p>
    434         * The default implementation passes control to the
    435         * <code>getSeriesShapesVisible</code> method. You can override this method
    436         * if you require different behaviour.
    437         *
    438         * @param series  the series index (zero-based).
    439         * @param item  the item index (zero-based).
    440         *
    441         * @return A boolean.
    442         */
    443        public boolean getItemShapeVisible(int series, int item) {
    444            Boolean flag = this.shapesVisible;
    445            if (flag == null) {
    446                flag = getSeriesShapesVisible(series);
    447            }
    448            if (flag != null) {
    449                return flag.booleanValue();
    450            }
    451            else {
    452                return this.baseShapesVisible;
    453            }
    454        }
    455
    456        /**
    457         * Returns the flag that controls whether the shapes are visible for the
    458         * items in ALL series.
    459         *
    460         * @return The flag (possibly <code>null</code>).
    461         *
    462         * @see #setShapesVisible(Boolean)
    463         *
    464         * @deprecated As of 1.0.7, use the per-series and base level settings.
    465         */
    466        public Boolean getShapesVisible() {
    467            return this.shapesVisible;
    468        }
    469
    470        /**
    471         * Sets the 'shapes visible' for ALL series and sends a
    472         * {@link RendererChangeEvent} to all registered listeners.
    473         *
    474         * @param visible  the flag (<code>null</code> permitted).
    475         *
    476         * @see #getShapesVisible()
    477         *
    478         * @deprecated As of 1.0.7, use the per-series and base level settings.
    479         */
    480        public void setShapesVisible(Boolean visible) {
    481            this.shapesVisible = visible;
    482            fireChangeEvent();
    483        }
    484
    485        /**
    486         * Sets the 'shapes visible' for ALL series and sends a
    487         * {@link RendererChangeEvent} to all registered listeners.
    488         *
    489         * @param visible  the flag.
    490         *
    491         * @see #getShapesVisible()
    492         *
    493         * @deprecated As of 1.0.7, use the per-series and base level settings.
    494         */
    495        public void setShapesVisible(boolean visible) {
    496            setShapesVisible(BooleanUtilities.valueOf(visible));
    497        }
    498
    499        /**
    500         * Returns the flag used to control whether or not the shapes for a series
    501         * are visible.
    502         *
    503         * @param series  the series index (zero-based).
    504         *
    505         * @return A boolean.
    506         *
    507         * @see #setSeriesShapesVisible(int, Boolean)
    508         */
    509        public Boolean getSeriesShapesVisible(int series) {
    510            return this.seriesShapesVisible.getBoolean(series);
    511        }
    512
    513        /**
    514         * Sets the 'shapes visible' flag for a series and sends a
    515         * {@link RendererChangeEvent} to all registered listeners.
    516         *
    517         * @param series  the series index (zero-based).
    518         * @param visible  the flag.
    519         *
    520         * @see #getSeriesShapesVisible(int)
    521         */
    522        public void setSeriesShapesVisible(int series, boolean visible) {
    523            setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
    524        }
    525
    526        /**
    527         * Sets the 'shapes visible' flag for a series and sends a
    528         * {@link RendererChangeEvent} to all registered listeners.
    529         *
    530         * @param series  the series index (zero-based).
    531         * @param flag  the flag.
    532         *
    533         * @see #getSeriesShapesVisible(int)
    534         */
    535        public void setSeriesShapesVisible(int series, Boolean flag) {
    536            this.seriesShapesVisible.setBoolean(series, flag);
    537            fireChangeEvent();
    538        }
    539
    540        /**
    541         * Returns the base 'shape visible' attribute.
    542         *
    543         * @return The base flag.
    544         *
    545         * @see #setBaseShapesVisible(boolean)
    546         */
    547        public boolean getBaseShapesVisible() {
    548            return this.baseShapesVisible;
    549        }
    550
    551        /**
    552         * Sets the base 'shapes visible' flag and sends a
    553         * {@link RendererChangeEvent} to all registered listeners.
    554         *
    555         * @param flag  the flag.
    556         *
    557         * @see #getBaseShapesVisible()
    558         */
    559        public void setBaseShapesVisible(boolean flag) {
    560            this.baseShapesVisible = flag;
    561            fireChangeEvent();
    562        }
    563
    564        // SHAPES FILLED
    565
    566        /**
    567         * Returns the flag used to control whether or not the shape for an item
    568         * is filled.
    569         * <p>
    570         * The default implementation passes control to the
    571         * <code>getSeriesShapesFilled</code> method. You can override this method
    572         * if you require different behaviour.
    573         *
    574         * @param series  the series index (zero-based).
    575         * @param item  the item index (zero-based).
    576         *
    577         * @return A boolean.
    578         */
    579        public boolean getItemShapeFilled(int series, int item) {
    580            Boolean flag = this.shapesFilled;
    581            if (flag == null) {
    582                flag = getSeriesShapesFilled(series);
    583            }
    584            if (flag != null) {
    585                return flag.booleanValue();
    586            }
    587            else {
    588                return this.baseShapesFilled;
    589            }
    590        }
    591
    592        /**
    593         * Sets the 'shapes filled' for ALL series and sends a
    594         * {@link RendererChangeEvent} to all registered listeners.
    595         *
    596         * @param filled  the flag.
    597         *
    598         * @deprecated As of 1.0.7, use the per-series and base level settings.
    599         */
    600        public void setShapesFilled(boolean filled) {
    601            setShapesFilled(BooleanUtilities.valueOf(filled));
    602        }
    603
    604        /**
    605         * Sets the 'shapes filled' for ALL series and sends a
    606         * {@link RendererChangeEvent} to all registered listeners.
    607         *
    608         * @param filled  the flag (<code>null</code> permitted).
    609         *
    610         * @deprecated As of 1.0.7, use the per-series and base level settings.
    611         */
    612        public void setShapesFilled(Boolean filled) {
    613            this.shapesFilled = filled;
    614            fireChangeEvent();
    615        }
    616
    617        /**
    618         * Returns the flag used to control whether or not the shapes for a series
    619         * are filled.
    620         *
    621         * @param series  the series index (zero-based).
    622         *
    623         * @return A boolean.
    624         *
    625         * @see #setSeriesShapesFilled(int, Boolean)
    626         */
    627        public Boolean getSeriesShapesFilled(int series) {
    628            return this.seriesShapesFilled.getBoolean(series);
    629        }
    630
    631        /**
    632         * Sets the 'shapes filled' flag for a series and sends a
    633         * {@link RendererChangeEvent} to all registered listeners.
    634         *
    635         * @param series  the series index (zero-based).
    636         * @param flag  the flag.
    637         *
    638         * @see #getSeriesShapesFilled(int)
    639         */
    640        public void setSeriesShapesFilled(int series, boolean flag) {
    641            setSeriesShapesFilled(series, BooleanUtilities.valueOf(flag));
    642        }
    643
    644        /**
    645         * Sets the 'shapes filled' flag for a series and sends a
    646         * {@link RendererChangeEvent} to all registered listeners.
    647         *
    648         * @param series  the series index (zero-based).
    649         * @param flag  the flag.
    650         *
    651         * @see #getSeriesShapesFilled(int)
    652         */
    653        public void setSeriesShapesFilled(int series, Boolean flag) {
    654            this.seriesShapesFilled.setBoolean(series, flag);
    655            fireChangeEvent();
    656        }
    657
    658        /**
    659         * Returns the base 'shape filled' attribute.
    660         *
    661         * @return The base flag.
    662         *
    663         * @see #setBaseShapesFilled(boolean)
    664         */
    665        public boolean getBaseShapesFilled() {
    666            return this.baseShapesFilled;
    667        }
    668
    669        /**
    670         * Sets the base 'shapes filled' flag and sends a
    671         * {@link RendererChangeEvent} to all registered listeners.
    672         *
    673         * @param flag  the flag.
    674         *
    675         * @see #getBaseShapesFilled()
    676         */
    677        public void setBaseShapesFilled(boolean flag) {
    678            this.baseShapesFilled = flag;
    679            fireChangeEvent();
    680        }
    681
    682        /**
    683         * Returns <code>true</code> if outlines should be drawn for shapes, and
    684         * <code>false</code> otherwise.
    685         *
    686         * @return A boolean.
    687         *
    688         * @see #setDrawOutlines(boolean)
    689         */
    690        public boolean getDrawOutlines() {
    691            return this.drawOutlines;
    692        }
    693
    694        /**
    695         * Sets the flag that controls whether outlines are drawn for
    696         * shapes, and sends a {@link RendererChangeEvent} to all registered
    697         * listeners.
    698         * <P>
    699         * In some cases, shapes look better if they do NOT have an outline, but
    700         * this flag allows you to set your own preference.
    701         *
    702         * @param flag  the flag.
    703         *
    704         * @see #getDrawOutlines()
    705         */
    706        public void setDrawOutlines(boolean flag) {
    707            this.drawOutlines = flag;
    708            fireChangeEvent();
    709        }
    710
    711        /**
    712         * Returns <code>true</code> if the renderer should use the fill paint
    713         * setting to fill shapes, and <code>false</code> if it should just
    714         * use the regular paint.
    715         * <p>
    716         * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the
    717         * effect of this flag.
    718         *
    719         * @return A boolean.
    720         *
    721         * @see #setUseFillPaint(boolean)
    722         * @see #getUseOutlinePaint()
    723         */
    724        public boolean getUseFillPaint() {
    725            return this.useFillPaint;
    726        }
    727
    728        /**
    729         * Sets the flag that controls whether the fill paint is used to fill
    730         * shapes, and sends a {@link RendererChangeEvent} to all
    731         * registered listeners.
    732         *
    733         * @param flag  the flag.
    734         *
    735         * @see #getUseFillPaint()
    736         */
    737        public void setUseFillPaint(boolean flag) {
    738            this.useFillPaint = flag;
    739            fireChangeEvent();
    740        }
    741
    742        /**
    743         * Returns <code>true</code> if the renderer should use the outline paint
    744         * setting to draw shape outlines, and <code>false</code> if it should just
    745         * use the regular paint.
    746         *
    747         * @return A boolean.
    748         *
    749         * @see #setUseOutlinePaint(boolean)
    750         * @see #getUseFillPaint()
    751         */
    752        public boolean getUseOutlinePaint() {
    753            return this.useOutlinePaint;
    754        }
    755
    756        /**
    757         * Sets the flag that controls whether the outline paint is used to draw
    758         * shape outlines, and sends a {@link RendererChangeEvent} to all
    759         * registered listeners.
    760         * <p>
    761         * Refer to <code>XYLineAndShapeRendererDemo2.java</code> to see the
    762         * effect of this flag.
    763         *
    764         * @param flag  the flag.
    765         *
    766         * @see #getUseOutlinePaint()
    767         */
    768        public void setUseOutlinePaint(boolean flag) {
    769            this.useOutlinePaint = flag;
    770            fireChangeEvent();
    771        }
    772
    773        /**
    774         * Records the state for the renderer.  This is used to preserve state
    775         * information between calls to the drawItem() method for a single chart
    776         * drawing.
    777         */
    778        public static class State extends XYItemRendererState {
    779
    780            /** The path for the current series. */
    781            public GeneralPath seriesPath;
    782
    783            /**
    784             * A flag that indicates if the last (x, y) point was 'good'
    785             * (non-null).
    786             */
    787            private boolean lastPointGood;
    788
    789            /**
    790             * Creates a new state instance.
    791             *
    792             * @param info  the plot rendering info.
    793             */
    794            public State(PlotRenderingInfo info) {
    795                super(info);
    796            }
    797
    798            /**
    799             * Returns a flag that indicates if the last point drawn (in the
    800             * current series) was 'good' (non-null).
    801             *
    802             * @return A boolean.
    803             */
    804            public boolean isLastPointGood() {
    805                return this.lastPointGood;
    806            }
    807
    808            /**
    809             * Sets a flag that indicates if the last point drawn (in the current
    810             * series) was 'good' (non-null).
    811             *
    812             * @param good  the flag.
    813             */
    814            public void setLastPointGood(boolean good) {
    815                this.lastPointGood = good;
    816            }
    817
    818            /**
    819             * This method is called by the {@link XYPlot} at the start of each
    820             * series pass.  We reset the state for the current series.
    821             *
    822             * @param dataset  the dataset.
    823             * @param series  the series index.
    824             * @param firstItem  the first item index for this pass.
    825             * @param lastItem  the last item index for this pass.
    826             * @param pass  the current pass index.
    827             * @param passCount  the number of passes.
    828             */
    829            public void startSeriesPass(XYDataset dataset, int series,
    830                    int firstItem, int lastItem, int pass, int passCount) {
    831                this.seriesPath.reset();
    832                this.lastPointGood = false;
    833                super.startSeriesPass(dataset, series, firstItem, lastItem, pass,
    834                        passCount);
    835           }
    836
    837        }
    838
    839        /**
    840         * Initialises the renderer.
    841         * <P>
    842         * This method will be called before the first item is rendered, giving the
    843         * renderer an opportunity to initialise any state information it wants to
    844         * maintain.  The renderer can do nothing if it chooses.
    845         *
    846         * @param g2  the graphics device.
    847         * @param dataArea  the area inside the axes.
    848         * @param plot  the plot.
    849         * @param data  the data.
    850         * @param info  an optional info collection object to return data back to
    851         *              the caller.
    852         *
    853         * @return The renderer state.
    854         */
    855        public XYItemRendererState initialise(Graphics2D g2,
    856                                              Rectangle2D dataArea,
    857                                              XYPlot plot,
    858                                              XYDataset data,
    859                                              PlotRenderingInfo info) {
    860
    861            State state = new State(info);
    862            state.seriesPath = new GeneralPath();
    863            return state;
    864
    865        }
    866
    867        /**
    868         * Draws the visual representation of a single data item.
    869         *
    870         * @param g2  the graphics device.
    871         * @param state  the renderer state.
    872         * @param dataArea  the area within which the data is being drawn.
    873         * @param info  collects information about the drawing.
    874         * @param plot  the plot (can be used to obtain standard color
    875         *              information etc).
    876         * @param domainAxis  the domain axis.
    877         * @param rangeAxis  the range axis.
    878         * @param dataset  the dataset.
    879         * @param series  the series index (zero-based).
    880         * @param item  the item index (zero-based).
    881         * @param crosshairState  crosshair information for the plot
    882         *                        (<code>null</code> permitted).
    883         * @param pass  the pass index.
    884         */
    885        public void drawItem(Graphics2D g2,
    886                             XYItemRendererState state,
    887                             Rectangle2D dataArea,
    888                             PlotRenderingInfo info,
    889                             XYPlot plot,
    890                             ValueAxis domainAxis,
    891                             ValueAxis rangeAxis,
    892                             XYDataset dataset,
    893                             int series,
    894                             int item,
    895                             CrosshairState crosshairState,
    896                             int pass) {
    897
    898            // do nothing if item is not visible
    899            if (!getItemVisible(series, item)) {
    900                return;
    901            }
    902
    903            // first pass draws the background (lines, for instance)
    904            if (isLinePass(pass)) {
    905                if (getItemLineVisible(series, item)) {
    906                    if (this.drawSeriesLineAsPath) {
    907                        drawPrimaryLineAsPath(state, g2, plot, dataset, pass,
    908                                series, item, domainAxis, rangeAxis, dataArea);
    909                    }
    910                    else {
    911                        drawPrimaryLine(state, g2, plot, dataset, pass, series,
    912                                item, domainAxis, rangeAxis, dataArea);
    913                    }
    914                }
    915            }
    916            // second pass adds shapes where the items are ..
    917            else if (isItemPass(pass)) {
    918
    919                // setup for collecting optional entity info...
    920                EntityCollection entities = null;
    921                if (info != null) {
    922                    entities = info.getOwner().getEntityCollection();
    923                }
    924
    925                drawSecondaryPass(g2, plot, dataset, pass, series, item,
    926                        domainAxis, dataArea, rangeAxis, crosshairState, entities);
    927            }
    928        }
    929
    930        /**
    931         * Returns <code>true</code> if the specified pass is the one for drawing
    932         * lines.
    933         *
    934         * @param pass  the pass.
    935         *
    936         * @return A boolean.
    937         */
    938        protected boolean isLinePass(int pass) {
    939            return pass == 0;
    940        }
    941
    942        /**
    943         * Returns <code>true</code> if the specified pass is the one for drawing
    944         * items.
    945         *
    946         * @param pass  the pass.
    947         *
    948         * @return A boolean.
    949         */
    950        protected boolean isItemPass(int pass) {
    951            return pass == 1;
    952        }
    953
    954        /**
    955         * Draws the item (first pass). This method draws the lines
    956         * connecting the items.
    957         *
    958         * @param g2  the graphics device.
    959         * @param state  the renderer state.
    960         * @param dataArea  the area within which the data is being drawn.
    961         * @param plot  the plot (can be used to obtain standard color
    962         *              information etc).
    963         * @param domainAxis  the domain axis.
    964         * @param rangeAxis  the range axis.
    965         * @param dataset  the dataset.
    966         * @param pass  the pass.
    967         * @param series  the series index (zero-based).
    968         * @param item  the item index (zero-based).
    969         */
    970        protected void drawPrimaryLine(XYItemRendererState state,
    971                                       Graphics2D g2,
    972                                       XYPlot plot,
    973                                       XYDataset dataset,
    974                                       int pass,
    975                                       int series,
    976                                       int item,
    977                                       ValueAxis domainAxis,
    978                                       ValueAxis rangeAxis,
    979                                       Rectangle2D dataArea) {
    980            if (item == 0) {
    981                return;
    982            }
    983
    984            // get the data point...
    985            double x1 = dataset.getXValue(series, item);
    986            double y1 = dataset.getYValue(series, item);
    987            if (Double.isNaN(y1) || Double.isNaN(x1)) {
    988                return;
    989            }
    990
    991            double x0 = dataset.getXValue(series, item - 1);
    992            double y0 = dataset.getYValue(series, item - 1);
    993            if (Double.isNaN(y0) || Double.isNaN(x0)) {
    994                return;
    995            }
    996
    997            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    998            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    999
    1000            double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
    1001            double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);
    1002
    1003            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
    1004            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
    1005
    1006            // only draw if we have good values
    1007            if (Double.isNaN(transX0) || Double.isNaN(transY0)
    1008                || Double.isNaN(transX1) || Double.isNaN(transY1)) {
    1009                return;
    1010            }
    1011
    1012            PlotOrientation orientation = plot.getOrientation();
    1013            if (orientation == PlotOrientation.HORIZONTAL) {
    1014                state.workingLine.setLine(transY0, transX0, transY1, transX1);
    1015            }
    1016            else if (orientation == PlotOrientation.VERTICAL) {
    1017                state.workingLine.setLine(transX0, transY0, transX1, transY1);
    1018            }
    1019
    1020            if (state.workingLine.intersects(dataArea)) {
    1021                drawFirstPassShape(g2, pass, series, item, state.workingLine);
    1022            }
    1023        }
    1024
    1025        /**
    1026         * Draws the first pass shape.
    1027         *
    1028         * @param g2  the graphics device.
    1029         * @param pass  the pass.
    1030         * @param series  the series index.
    1031         * @param item  the item index.
    1032         * @param shape  the shape.
    1033         */
    1034        protected void drawFirstPassShape(Graphics2D g2, int pass, int series,
    1035                                          int item, Shape shape) {
    1036            g2.setStroke(getItemStroke(series, item));
    1037            g2.setPaint(getItemPaint(series, item));
    1038            g2.draw(shape);
    1039        }
    1040
    1041
    1042        /**
    1043         * Draws the item (first pass). This method draws the lines
    1044         * connecting the items. Instead of drawing separate lines,
    1045         * a GeneralPath is constructed and drawn at the end of
    1046         * the series painting.
    1047         *
    1048         * @param g2  the graphics device.
    1049         * @param state  the renderer state.
    1050         * @param plot  the plot (can be used to obtain standard color information
    1051         *              etc).
    1052         * @param dataset  the dataset.
    1053         * @param pass  the pass.
    1054         * @param series  the series index (zero-based).
    1055         * @param item  the item index (zero-based).
    1056         * @param domainAxis  the domain axis.
    1057         * @param rangeAxis  the range axis.
    1058         * @param dataArea  the area within which the data is being drawn.
    1059         */
    1060        protected void drawPrimaryLineAsPath(XYItemRendererState state,
    1061                                             Graphics2D g2, XYPlot plot,
    1062                                             XYDataset dataset,
    1063                                             int pass,
    1064                                             int series,
    1065                                             int item,
    1066                                             ValueAxis domainAxis,
    1067                                             ValueAxis rangeAxis,
    1068                                             Rectangle2D dataArea) {
    1069
    1070
    1071            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    1072            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    1073
    1074            // get the data point...
    1075            double x1 = dataset.getXValue(series, item);
    1076            double y1 = dataset.getYValue(series, item);
    1077            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
    1078            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
    1079
    1080            State s = (State) state;
    1081            // update path to reflect latest point
    1082            if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
    1083                float x = (float) transX1;
    1084                float y = (float) transY1;
    1085                PlotOrientation orientation = plot.getOrientation();
    1086                if (orientation == PlotOrientation.HORIZONTAL) {
    1087                    x = (float) transY1;
    1088                    y = (float) transX1;
    1089                }
    1090                if (s.isLastPointGood()) {
    1091                    s.seriesPath.lineTo(x, y);
    1092                }
    1093                else {
    1094                    s.seriesPath.moveTo(x, y);
    1095                }
    1096                s.setLastPointGood(true);
    1097            }
    1098            else {
    1099                s.setLastPointGood(false);
    1100            }
    1101            // if this is the last item, draw the path ...
    1102            if (item == s.getLastItemIndex()) {
    1103                // draw path
    1104                drawFirstPassShape(g2, pass, series, item, s.seriesPath);
    1105            }
    1106        }
    1107
    1108        /**
    1109         * Draws the item shapes and adds chart entities (second pass). This method
    1110         * draws the shapes which mark the item positions. If <code>entities</code>
    1111         * is not <code>null</code> it will be populated with entity information
    1112         * for points that fall within the data area.
    1113         *
    1114         * @param g2  the graphics device.
    1115         * @param plot  the plot (can be used to obtain standard color
    1116         *              information etc).
    1117         * @param domainAxis  the domain axis.
    1118         * @param dataArea  the area within which the data is being drawn.
    1119         * @param rangeAxis  the range axis.
    1120         * @param dataset  the dataset.
    1121         * @param pass  the pass.
    1122         * @param series  the series index (zero-based).
    1123         * @param item  the item index (zero-based).
    1124         * @param crosshairState  the crosshair state.
    1125         * @param entities the entity collection.
    1126         */
    1127        protected void drawSecondaryPass(Graphics2D g2, XYPlot plot,
    1128                                         XYDataset dataset,
    1129                                         int pass, int series, int item,
    1130                                         ValueAxis domainAxis,
    1131                                         Rectangle2D dataArea,
    1132                                         ValueAxis rangeAxis,
    1133                                         CrosshairState crosshairState,
    1134                                         EntityCollection entities) {
    1135
    1136            Shape entityArea = null;
    1137
    1138            // get the data point...
    1139            double x1 = dataset.getXValue(series, item);
    1140            double y1 = dataset.getYValue(series, item);
    1141            if (Double.isNaN(y1) || Double.isNaN(x1)) {
    1142                return;
    1143            }
    1144
    1145            PlotOrientation orientation = plot.getOrientation();
    1146            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    1147            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    1148            double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
    1149            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
    1150
    1151            if (getItemShapeVisible(series, item)) {
    1152                Shape shape = getItemShape(series, item);
    1153                if (orientation == PlotOrientation.HORIZONTAL) {
    1154                    shape = ShapeUtilities.createTranslatedShape(shape, transY1,
    1155                            transX1);
    1156                }
    1157                else if (orientation == PlotOrientation.VERTICAL) {
    1158                    shape = ShapeUtilities.createTranslatedShape(shape, transX1,
    1159                            transY1);
    1160                }
    1161                entityArea = shape;
    1162                if (shape.intersects(dataArea)) {
    1163                    if (getItemShapeFilled(series, item)) {
    1164                        if (this.useFillPaint) {
    1165                            g2.setPaint(getItemFillPaint(series, item));
    1166                        }
    1167                        else {
    1168                            g2.setPaint(getItemPaint(series, item));
    1169                        }
    1170                        g2.fill(shape);
    1171                    }
    1172                    if (this.drawOutlines) {
    1173                        if (getUseOutlinePaint()) {
    1174                            g2.setPaint(getItemOutlinePaint(series, item));
    1175                        }
    1176                        else {
    1177                            g2.setPaint(getItemPaint(series, item));
    1178                        }
    1179                        g2.setStroke(getItemOutlineStroke(series, item));
    1180                        g2.draw(shape);
    1181                    }
    1182                }
    1183            }
    1184
    1185            double xx = transX1;
    1186            double yy = transY1;
    1187            if (orientation == PlotOrientation.HORIZONTAL) {
    1188                xx = transY1;
    1189                yy = transX1;
    1190            }
    1191
    1192            // draw the item label if there is one...
    1193            if (isItemLabelVisible(series, item)) {
    1194                drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
    1195                        (y1 < 0.0));
    1196            }
    1197
    1198            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    1199            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    1200            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
    1201                    rangeAxisIndex, transX1, transY1, orientation);
    1202
    1203            // add an entity for the item, but only if it falls within the data
    1204            // area...
    1205            if (entities != null && isPointInRect(dataArea, xx, yy)) {
    1206                addEntity(entities, entityArea, dataset, series, item, xx, yy);
    1207            }
    1208        }
    1209
    1210
    1211        /**
    1212         * Returns a legend item for the specified series.
    1213         *
    1214         * @param datasetIndex  the dataset index (zero-based).
    1215         * @param series  the series index (zero-based).
    1216         *
    1217         * @return A legend item for the series.
    1218         */
    1219        public LegendItem getLegendItem(int datasetIndex, int series) {
    1220
    1221            XYPlot plot = getPlot();
    1222            if (plot == null) {
    1223                return null;
    1224            }
    1225
    1226            LegendItem result = null;
    1227            XYDataset dataset = plot.getDataset(datasetIndex);
    1228            if (dataset != null) {
    1229                if (getItemVisible(series, 0)) {
    1230                    String label = getLegendItemLabelGenerator().generateLabel(
    1231                            dataset, series);
    1232                    String description = label;
    1233                    String toolTipText = null;
    1234                    if (getLegendItemToolTipGenerator() != null) {
    1235                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
    1236                                dataset, series);
    1237                    }
    1238                    String urlText = null;
    1239                    if (getLegendItemURLGenerator() != null) {
    1240                        urlText = getLegendItemURLGenerator().generateLabel(
    1241                                dataset, series);
    1242                    }
    1243                    boolean shapeIsVisible = getItemShapeVisible(series, 0);
    1244                    Shape shape = lookupLegendShape(series);
    1245                    boolean shapeIsFilled = getItemShapeFilled(series, 0);
    1246                    Paint fillPaint = (this.useFillPaint
    1247                        ? lookupSeriesFillPaint(series)
    1248                        : lookupSeriesPaint(series));
    1249                    boolean shapeOutlineVisible = this.drawOutlines;
    1250                    Paint outlinePaint = (this.useOutlinePaint
    1251                        ? lookupSeriesOutlinePaint(series)
    1252                        : lookupSeriesPaint(series));
    1253                    Stroke outlineStroke = lookupSeriesOutlineStroke(series);
    1254                    boolean lineVisible = getItemLineVisible(series, 0);
    1255                    Stroke lineStroke = lookupSeriesStroke(series);
    1256                    Paint linePaint = lookupSeriesPaint(series);
    1257                    result = new LegendItem(label, description, toolTipText,
    1258                            urlText, shapeIsVisible, shape, shapeIsFilled,
    1259                            fillPaint, shapeOutlineVisible, outlinePaint,
    1260                            outlineStroke, lineVisible, this.legendLine,
    1261                            lineStroke, linePaint);
    1262                    result.setLabelFont(lookupLegendTextFont(series));
    1263                    Paint labelPaint = lookupLegendTextPaint(series);
    1264                    if (labelPaint != null) {
    1265                        result.setLabelPaint(labelPaint);
    1266                    }
    1267                    result.setSeriesKey(dataset.getSeriesKey(series));
    1268                    result.setSeriesIndex(series);
    1269                    result.setDataset(dataset);
    1270                    result.setDatasetIndex(datasetIndex);
    1271                }
    1272            }
    1273
    1274            return result;
    1275
    1276        }
    1277
    1278        /**
    1279         * Returns a clone of the renderer.
    1280         *
    1281         * @return A clone.
    1282         *
    1283         * @throws CloneNotSupportedException if the clone cannot be created.
    1284         */
    1285        public Object clone() throws CloneNotSupportedException {
    1286            XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone();
    1287            clone.seriesLinesVisible
    1288                    = (BooleanList) this.seriesLinesVisible.clone();
    1289            if (this.legendLine != null) {
    1290                clone.legendLine = ShapeUtilities.clone(this.legendLine);
    1291            }
    1292            clone.seriesShapesVisible
    1293                    = (BooleanList) this.seriesShapesVisible.clone();
    1294            clone.seriesShapesFilled
    1295                    = (BooleanList) this.seriesShapesFilled.clone();
    1296            return clone;
    1297        }
    1298
    1299        /**
    1300         * Tests this renderer for equality with an arbitrary object.
    1301         *
    1302         * @param obj  the object (<code>null</code> permitted).
    1303         *
    1304         * @return <code>true</code> or <code>false</code>.
    1305         */
    1306        public boolean equals(Object obj) {
    1307            if (obj == this) {
    1308                return true;
    1309            }
    1310            if (!(obj instanceof XYLineAndShapeRenderer)) {
    1311                return false;
    1312            }
    1313            if (!super.equals(obj)) {
    1314                return false;
    1315            }
    1316            XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj;
    1317            if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
    1318                return false;
    1319            }
    1320            if (!ObjectUtilities.equal(
    1321                this.seriesLinesVisible, that.seriesLinesVisible)
    1322            ) {
    1323                return false;
    1324            }
    1325            if (this.baseLinesVisible != that.baseLinesVisible) {
    1326                return false;
    1327            }
    1328            if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
    1329                return false;
    1330            }
    1331            if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
    1332                return false;
    1333            }
    1334            if (!ObjectUtilities.equal(
    1335                this.seriesShapesVisible, that.seriesShapesVisible)
    1336            ) {
    1337                return false;
    1338            }
    1339            if (this.baseShapesVisible != that.baseShapesVisible) {
    1340                return false;
    1341            }
    1342            if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
    1343                return false;
    1344            }
    1345            if (!ObjectUtilities.equal(
    1346                this.seriesShapesFilled, that.seriesShapesFilled)
    1347            ) {
    1348                return false;
    1349            }
    1350            if (this.baseShapesFilled != that.baseShapesFilled) {
    1351                return false;
    1352            }
    1353            if (this.drawOutlines != that.drawOutlines) {
    1354                return false;
    1355            }
    1356            if (this.useOutlinePaint != that.useOutlinePaint) {
    1357                return false;
    1358            }
    1359            if (this.useFillPaint != that.useFillPaint) {
    1360                return false;
    1361            }
    1362            if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
    1363                return false;
    1364            }
    1365            return true;
    1366        }
    1367
    1368        /**
    1369         * Provides serialization support.
    1370         *
    1371         * @param stream  the input stream.
    1372         *
    1373         * @throws IOException  if there is an I/O error.
    1374         * @throws ClassNotFoundException  if there is a classpath problem.
    1375         */
    1376        private void readObject(ObjectInputStream stream)
    1377                throws IOException, ClassNotFoundException {
    1378            stream.defaultReadObject();
    1379            this.legendLine = SerialUtilities.readShape(stream);
    1380        }
    1381
    1382        /**
    1383         * Provides serialization support.
    1384         *
    1385         * @param stream  the output stream.
    1386         *
    1387         * @throws IOException  if there is an I/O error.
    1388         */
    1389        private void writeObject(ObjectOutputStream stream) throws IOException {
    1390            stream.defaultWriteObject();
    1391            SerialUtilities.writeShape(this.legendLine, stream);
    1392        }
    1393
    1394    }