    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
    071package org.jfree.chart.renderer.xy;
    072
    073import java.awt.Graphics2D;
    074import java.awt.Paint;
    075import java.awt.Shape;
    076import java.awt.Stroke;
    077import java.awt.geom.GeneralPath;
    078import java.awt.geom.Line2D;
    079import java.awt.geom.Rectangle2D;
    080import java.io.IOException;
    081import java.io.ObjectInputStream;
    082import java.io.ObjectOutputStream;
    083import java.io.Serializable;
    084
    085import org.jfree.chart.LegendItem;
    086import org.jfree.chart.axis.ValueAxis;
    087import org.jfree.chart.entity.EntityCollection;
    088import org.jfree.chart.event.RendererChangeEvent;
    089import org.jfree.chart.plot.CrosshairState;
    090import org.jfree.chart.plot.PlotOrientation;
    091import org.jfree.chart.plot.PlotRenderingInfo;
    092import org.jfree.chart.plot.XYPlot;
    093import org.jfree.chart.util.LineUtilities;
    094import org.jfree.data.xy.XYDataset;
    095import org.jfree.io.SerialUtilities;
    096import org.jfree.ui.RectangleEdge;
    097import org.jfree.util.BooleanList;
    098import org.jfree.util.BooleanUtilities;
    099import org.jfree.util.ObjectUtilities;
    100import org.jfree.util.PublicCloneable;
    101import 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 */
    114public 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}