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