    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 * StackedXYAreaRenderer.java
    029 * --------------------------
    030 * (C) Copyright 2003-2008, by Richard Atkinson and Contributors.
    031 *
    032 * Original Author:  Richard Atkinson;
    033 * Contributor(s):   Christian W. Zuckschwerdt;
    034 *                   David Gilbert (for Object Refinery Limited);
    035 *
    036 * Changes:
    037 * --------
    038 * 27-Jul-2003 : Initial version (RA);
    039 * 30-Jul-2003 : Modified entity constructor (CZ);
    040 * 18-Aug-2003 : Now handles null values (RA);
    041 * 20-Aug-2003 : Implemented Cloneable, PublicCloneable and Serializable (DG);
    042 * 22-Sep-2003 : Changed to be a two pass renderer with optional shape Paint
    043 *               and Stroke (RA);
    044 * 07-Oct-2003 : Added renderer state (DG);
    045 * 10-Feb-2004 : Updated state object and changed drawItem() method to make
    046 *               overriding easier (DG);
    047 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    048 *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    049 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    050 *               getYValue() (DG);
    051 * 10-Sep-2004 : Removed getRangeType() method (DG);
    052 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    053 * 06-Jan-2005 : Override equals() (DG);
    054 * 07-Jan-2005 : Update for method name changes in DatasetUtilities (DG);
    055 * 28-Mar-2005 : Use getXValue() and getYValue() from dataset (DG);
    056 * 06-Jun-2005 : Fixed null pointer exception, plus problems with equals() and
    057 *               serialization (DG);
    058 * ------------- JFREECHART 1.0.x ---------------------------------------------
    059 * 10-Nov-2006 : Fixed bug 1593156, NullPointerException with line
    060 *               plotting (DG);
    061 * 02-Feb-2007 : Fixed bug 1649686, crosshairs don't stack y-values (DG);
    062 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    063 * 22-Mar-2007 : Fire change events in setShapePaint() and setShapeStroke()
    064 *               methods (DG);
    065 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    066 *
    067 */
    068
    069package org.jfree.chart.renderer.xy;
    070
    071import java.awt.Graphics2D;
    072import java.awt.Paint;
    073import java.awt.Point;
    074import java.awt.Polygon;
    075import java.awt.Shape;
    076import java.awt.Stroke;
    077import java.awt.geom.Line2D;
    078import java.awt.geom.Rectangle2D;
    079import java.io.IOException;
    080import java.io.ObjectInputStream;
    081import java.io.ObjectOutputStream;
    082import java.io.Serializable;
    083import java.util.Stack;
    084
    085import org.jfree.chart.axis.ValueAxis;
    086import org.jfree.chart.entity.EntityCollection;
    087import org.jfree.chart.entity.XYItemEntity;
    088import org.jfree.chart.event.RendererChangeEvent;
    089import org.jfree.chart.labels.XYToolTipGenerator;
    090import org.jfree.chart.plot.CrosshairState;
    091import org.jfree.chart.plot.PlotOrientation;
    092import org.jfree.chart.plot.PlotRenderingInfo;
    093import org.jfree.chart.plot.XYPlot;
    094import org.jfree.chart.urls.XYURLGenerator;
    095import org.jfree.data.Range;
    096import org.jfree.data.general.DatasetUtilities;
    097import org.jfree.data.xy.TableXYDataset;
    098import org.jfree.data.xy.XYDataset;
    099import org.jfree.io.SerialUtilities;
    100import org.jfree.util.ObjectUtilities;
    101import org.jfree.util.PaintUtilities;
    102import org.jfree.util.PublicCloneable;
    103import org.jfree.util.ShapeUtilities;
    104
    105/**
    106 * A stacked area renderer for the {@link XYPlot} class.
    107 * <br><br>
    108 * The example shown here is generated by the
    109 * <code>StackedXYAreaRendererDemo1.java</code> program included in the
    110 * JFreeChart demo collection:
    111 * <br><br>
    112 * <img src="../../../../../images/StackedXYAreaRendererSample.png"
    113 * alt="StackedXYAreaRendererSample.png" />
    114 * <br><br>
    115 * SPECIAL NOTE:  This renderer does not currently handle negative data values
    116 * correctly.  This should get fixed at some point, but the current workaround
    117 * is to use the {@link StackedXYAreaRenderer2} class instead.
    118 */
    119public class StackedXYAreaRenderer extends XYAreaRenderer
    120        implements Cloneable, PublicCloneable, Serializable {
    121
    122    /** For serialization. */
    123    private static final long serialVersionUID = 5217394318178570889L;
    124
    125     /**
    126     * A state object for use by this renderer.
    127     */
    128    static class StackedXYAreaRendererState extends XYItemRendererState {
    129
    130        /** The area for the current series. */
    131        private Polygon seriesArea;
    132
    133        /** The line. */
    134        private Line2D line;
    135
    136        /** The points from the last series. */
    137        private Stack lastSeriesPoints;
    138
    139        /** The points for the current series. */
    140        private Stack currentSeriesPoints;
    141
    142        /**
    143         * Creates a new state for the renderer.
    144         *
    145         * @param info  the plot rendering info.
    146         */
    147        public StackedXYAreaRendererState(PlotRenderingInfo info) {
    148            super(info);
    149            this.seriesArea = null;
    150            this.line = new Line2D.Double();
    151            this.lastSeriesPoints = new Stack();
    152            this.currentSeriesPoints = new Stack();
    153        }
    154
    155        /**
    156         * Returns the series area.
    157         *
    158         * @return The series area.
    159         */
    160        public Polygon getSeriesArea() {
    161            return this.seriesArea;
    162        }
    163
    164        /**
    165         * Sets the series area.
    166         *
    167         * @param area  the area.
    168         */
    169        public void setSeriesArea(Polygon area) {
    170            this.seriesArea = area;
    171        }
    172
    173        /**
    174         * Returns the working line.
    175         *
    176         * @return The working line.
    177         */
    178        public Line2D getLine() {
    179            return this.line;
    180        }
    181
    182        /**
    183         * Returns the current series points.
    184         *
    185         * @return The current series points.
    186         */
    187        public Stack getCurrentSeriesPoints() {
    188            return this.currentSeriesPoints;
    189        }
    190
    191        /**
    192         * Sets the current series points.
    193         *
    194         * @param points  the points.
    195         */
    196        public void setCurrentSeriesPoints(Stack points) {
    197            this.currentSeriesPoints = points;
    198        }
    199
    200        /**
    201         * Returns the last series points.
    202         *
    203         * @return The last series points.
    204         */
    205        public Stack getLastSeriesPoints() {
    206            return this.lastSeriesPoints;
    207        }
    208
    209        /**
    210         * Sets the last series points.
    211         *
    212         * @param points  the points.
    213         */
    214        public void setLastSeriesPoints(Stack points) {
    215            this.lastSeriesPoints = points;
    216        }
    217
    218    }
    219
    220    /**
    221     * Custom Paint for drawing all shapes, if null defaults to series shapes
    222     */
    223    private transient Paint shapePaint = null;
    224
    225    /**
    226     * Custom Stroke for drawing all shapes, if null defaults to series
    227     * strokes.
    228     */
    229    private transient Stroke shapeStroke = null;
    230
    231    /**
    232     * Creates a new renderer.
    233     */
    234    public StackedXYAreaRenderer() {
    235        this(AREA);
    236    }
    237
    238    /**
    239     * Constructs a new renderer.
    240     *
    241     * @param type  the type of the renderer.
    242     */
    243    public StackedXYAreaRenderer(int type) {
    244        this(type, null, null);
    245    }
    246
    247    /**
    248     * Constructs a new renderer.  To specify the type of renderer, use one of
    249     * the constants: <code>SHAPES</code>, <code>LINES</code>,
    250     * <code>SHAPES_AND_LINES</code>, <code>AREA</code> or
    251     * <code>AREA_AND_SHAPES</code>.
    252     *
    253     * @param type  the type of renderer.
    254     * @param labelGenerator  the tool tip generator to use (<code>null</code>
    255     *                        is none).
    256     * @param urlGenerator  the URL generator (<code>null</code> permitted).
    257     */
    258    public StackedXYAreaRenderer(int type,
    259                                 XYToolTipGenerator labelGenerator,
    260                                 XYURLGenerator urlGenerator) {
    261
    262        super(type, labelGenerator, urlGenerator);
    263    }
    264
    265    /**
    266     * Returns the paint used for rendering shapes, or <code>null</code> if
    267     * using series paints.
    268     *
    269     * @return The paint (possibly <code>null</code>).
    270     *
    271     * @see #setShapePaint(Paint)
    272     */
    273    public Paint getShapePaint() {
    274        return this.shapePaint;
    275    }
    276
    277    /**
    278     * Sets the paint for rendering shapes and sends a
    279     * {@link RendererChangeEvent} to all registered listeners.
    280     *
    281     * @param shapePaint  the paint (<code>null</code> permitted).
    282     *
    283     * @see #getShapePaint()
    284     */
    285    public void setShapePaint(Paint shapePaint) {
    286        this.shapePaint = shapePaint;
    287        fireChangeEvent();
    288    }
    289
    290    /**
    291     * Returns the stroke used for rendering shapes, or <code>null</code> if
    292     * using series strokes.
    293     *
    294     * @return The stroke (possibly <code>null</code>).
    295     *
    296     * @see #setShapeStroke(Stroke)
    297     */
    298    public Stroke getShapeStroke() {
    299        return this.shapeStroke;
    300    }
    301
    302    /**
    303     * Sets the stroke for rendering shapes and sends a
    304     * {@link RendererChangeEvent} to all registered listeners.
    305     *
    306     * @param shapeStroke  the stroke (<code>null</code> permitted).
    307     *
    308     * @see #getShapeStroke()
    309     */
    310    public void setShapeStroke(Stroke shapeStroke) {
    311        this.shapeStroke = shapeStroke;
    312        fireChangeEvent();
    313    }
    314
    315    /**
    316     * Initialises the renderer. This method will be called before the first
    317     * item is rendered, giving the renderer an opportunity to initialise any
    318     * state information it wants to maintain.
    319     *
    320     * @param g2  the graphics device.
    321     * @param dataArea  the area inside the axes.
    322     * @param plot  the plot.
    323     * @param data  the data.
    324     * @param info  an optional info collection object to return data back to
    325     *              the caller.
    326     *
    327     * @return A state object that should be passed to subsequent calls to the
    328     *         drawItem() method.
    329     */
    330    public XYItemRendererState initialise(Graphics2D g2,
    331                                          Rectangle2D dataArea,
    332                                          XYPlot plot,
    333                                          XYDataset data,
    334                                          PlotRenderingInfo info) {
    335
    336        XYItemRendererState state = new StackedXYAreaRendererState(info);
    337        // in the rendering process, there is special handling for item
    338        // zero, so we can't support processing of visible data items only
    339        state.setProcessVisibleItemsOnly(false);
    340        return state;
    341    }
    342
    343    /**
    344     * Returns the number of passes required by the renderer.
    345     *
    346     * @return 2.
    347     */
    348    public int getPassCount() {
    349        return 2;
    350    }
    351
    352    /**
    353     * Returns the range of values the renderer requires to display all the
    354     * items from the specified dataset.
    355     *
    356     * @param dataset  the dataset (<code>null</code> permitted).
    357     *
    358     * @return The range ([0.0, 0.0] if the dataset contains no values, and
    359     *         <code>null</code> if the dataset is <code>null</code>).
    360     *
    361     * @throws ClassCastException if <code>dataset</code> is not an instance
    362     *         of {@link TableXYDataset}.
    363     */
    364    public Range findRangeBounds(XYDataset dataset) {
    365        if (dataset != null) {
    366            return DatasetUtilities.findStackedRangeBounds(
    367                (TableXYDataset) dataset);
    368        }
    369        else {
    370            return null;
    371        }
    372    }
    373
    374    /**
    375     * Draws the visual representation of a single data item.
    376     *
    377     * @param g2  the graphics device.
    378     * @param state  the renderer state.
    379     * @param dataArea  the area within which the data is being drawn.
    380     * @param info  collects information about the drawing.
    381     * @param plot  the plot (can be used to obtain standard color information
    382     *              etc).
    383     * @param domainAxis  the domain axis.
    384     * @param rangeAxis  the range axis.
    385     * @param dataset  the dataset.
    386     * @param series  the series index (zero-based).
    387     * @param item  the item index (zero-based).
    388     * @param crosshairState  information about crosshairs on a plot.
    389     * @param pass  the pass index.
    390     *
    391     * @throws ClassCastException if <code>state</code> is not an instance of
    392     *         <code>StackedXYAreaRendererState</code> or <code>dataset</code>
    393     *         is not an instance of {@link TableXYDataset}.
    394     */
    395    public void drawItem(Graphics2D g2,
    396                         XYItemRendererState state,
    397                         Rectangle2D dataArea,
    398                         PlotRenderingInfo info,
    399                         XYPlot plot,
    400                         ValueAxis domainAxis,
    401                         ValueAxis rangeAxis,
    402                         XYDataset dataset,
    403                         int series,
    404                         int item,
    405                         CrosshairState crosshairState,
    406                         int pass) {
    407
    408        PlotOrientation orientation = plot.getOrientation();
    409        StackedXYAreaRendererState areaState
    410            = (StackedXYAreaRendererState) state;
    411        // Get the item count for the series, so that we can know which is the
    412        // end of the series.
    413        TableXYDataset tdataset = (TableXYDataset) dataset;
    414        int itemCount = tdataset.getItemCount();
    415
    416        // get the data point...
    417        double x1 = dataset.getXValue(series, item);
    418        double y1 = dataset.getYValue(series, item);
    419        boolean nullPoint = false;
    420        if (Double.isNaN(y1)) {
    421            y1 = 0.0;
    422            nullPoint = true;
    423        }
    424
    425        //  Get height adjustment based on stack and translate to Java2D values
    426        double ph1 = getPreviousHeight(tdataset, series, item);
    427        double transX1 = domainAxis.valueToJava2D(x1, dataArea,
    428                plot.getDomainAxisEdge());
    429        double transY1 = rangeAxis.valueToJava2D(y1 + ph1, dataArea,
    430                plot.getRangeAxisEdge());
    431
    432        //  Get series Paint and Stroke
    433        Paint seriesPaint = getItemPaint(series, item);
    434        Stroke seriesStroke = getItemStroke(series, item);
    435
    436        if (pass == 0) {
    437            //  On first pass render the areas, line and outlines
    438
    439            if (item == 0) {
    440                // Create a new Area for the series
    441                areaState.setSeriesArea(new Polygon());
    442                areaState.setLastSeriesPoints(
    443                        areaState.getCurrentSeriesPoints());
    444                areaState.setCurrentSeriesPoints(new Stack());
    445
    446                // start from previous height (ph1)
    447                double transY2 = rangeAxis.valueToJava2D(ph1, dataArea,
    448                        plot.getRangeAxisEdge());
    449
    450                // The first point is (x, 0)
    451                if (orientation == PlotOrientation.VERTICAL) {
    452                    areaState.getSeriesArea().addPoint((int) transX1,
    453                            (int) transY2);
    454                }
    455                else if (orientation == PlotOrientation.HORIZONTAL) {
    456                    areaState.getSeriesArea().addPoint((int) transY2,
    457                            (int) transX1);
    458                }
    459            }
    460
    461            // Add each point to Area (x, y)
    462            if (orientation == PlotOrientation.VERTICAL) {
    463                Point point = new Point((int) transX1, (int) transY1);
    464                areaState.getSeriesArea().addPoint((int) point.getX(),
    465                        (int) point.getY());
    466                areaState.getCurrentSeriesPoints().push(point);
    467            }
    468            else if (orientation == PlotOrientation.HORIZONTAL) {
    469                areaState.getSeriesArea().addPoint((int) transY1,
    470                        (int) transX1);
    471            }
    472
    473            if (getPlotLines()) {
    474                if (item > 0) {
    475                    // get the previous data point...
    476                    double x0 = dataset.getXValue(series, item - 1);
    477                    double y0 = dataset.getYValue(series, item - 1);
    478                    double ph0 = getPreviousHeight(tdataset, series, item - 1);
    479                    double transX0 = domainAxis.valueToJava2D(x0, dataArea,
    480                            plot.getDomainAxisEdge());
    481                    double transY0 = rangeAxis.valueToJava2D(y0 + ph0,
    482                            dataArea, plot.getRangeAxisEdge());
    483
    484                    if (orientation == PlotOrientation.VERTICAL) {
    485                        areaState.getLine().setLine(transX0, transY0, transX1,
    486                                transY1);
    487                    }
    488                    else if (orientation == PlotOrientation.HORIZONTAL) {
    489                        areaState.getLine().setLine(transY0, transX0, transY1,
    490                                transX1);
    491                    }
    492                    g2.draw(areaState.getLine());
    493                }
    494            }
    495
    496            // Check if the item is the last item for the series and number of
    497            // items > 0.  We can't draw an area for a single point.
    498            if (getPlotArea() && item > 0 && item == (itemCount - 1)) {
    499
    500                double transY2 = rangeAxis.valueToJava2D(ph1, dataArea,
    501                        plot.getRangeAxisEdge());
    502
    503                if (orientation == PlotOrientation.VERTICAL) {
    504                    // Add the last point (x,0)
    505                    areaState.getSeriesArea().addPoint((int) transX1,
    506                            (int) transY2);
    507                }
    508                else if (orientation == PlotOrientation.HORIZONTAL) {
    509                    // Add the last point (x,0)
    510                    areaState.getSeriesArea().addPoint((int) transY2,
    511                            (int) transX1);
    512                }
    513
    514                // Add points from last series to complete the base of the
    515                // polygon
    516                if (series != 0) {
    517                    Stack points = areaState.getLastSeriesPoints();
    518                    while (!points.empty()) {
    519                        Point point = (Point) points.pop();
    520                        areaState.getSeriesArea().addPoint((int) point.getX(),
    521                                (int) point.getY());
    522                    }
    523                }
    524
    525                //  Fill the polygon
    526                g2.setPaint(seriesPaint);
    527                g2.setStroke(seriesStroke);
    528                g2.fill(areaState.getSeriesArea());
    529
    530                //  Draw an outline around the Area.
    531                if (isOutline()) {
    532                    g2.setStroke(lookupSeriesOutlineStroke(series));
    533                    g2.setPaint(lookupSeriesOutlinePaint(series));
    534                    g2.draw(areaState.getSeriesArea());
    535                }
    536            }
    537
    538            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    539            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    540            updateCrosshairValues(crosshairState, x1, ph1 + y1, domainAxisIndex,
    541                    rangeAxisIndex, transX1, transY1, orientation);
    542
    543        }
    544        else if (pass == 1) {
    545            // On second pass render shapes and collect entity and tooltip
    546            // information
    547
    548            Shape shape = null;
    549            if (getPlotShapes()) {
    550                shape = getItemShape(series, item);
    551                if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    552                    shape = ShapeUtilities.createTranslatedShape(shape,
    553                            transX1, transY1);
    554                }
    555                else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    556                    shape = ShapeUtilities.createTranslatedShape(shape,
    557                            transY1, transX1);
    558                }
    559                if (!nullPoint) {
    560                    if (getShapePaint() != null) {
    561                        g2.setPaint(getShapePaint());
    562                    }
    563                    else {
    564                        g2.setPaint(seriesPaint);
    565                    }
    566                    if (getShapeStroke() != null) {
    567                        g2.setStroke(getShapeStroke());
    568                    }
    569                    else {
    570                        g2.setStroke(seriesStroke);
    571                    }
    572                    g2.draw(shape);
    573                }
    574            }
    575            else {
    576                if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    577                    shape = new Rectangle2D.Double(transX1 - 3, transY1 - 3,
    578                            6.0, 6.0);
    579                }
    580                else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    581                    shape = new Rectangle2D.Double(transY1 - 3, transX1 - 3,
    582                            6.0, 6.0);
    583                }
    584            }
    585
    586            // collect entity and tool tip information...
    587            if (state.getInfo() != null) {
    588                EntityCollection entities = state.getEntityCollection();
    589                if (entities != null && shape != null && !nullPoint) {
    590                    String tip = null;
    591                    XYToolTipGenerator generator
    592                        = getToolTipGenerator(series, item);
    593                    if (generator != null) {
    594                        tip = generator.generateToolTip(dataset, series, item);
    595                    }
    596                    String url = null;
    597                    if (getURLGenerator() != null) {
    598                        url = getURLGenerator().generateURL(dataset, series,
    599                                item);
    600                    }
    601                    XYItemEntity entity = new XYItemEntity(shape, dataset,
    602                            series, item, tip, url);
    603                    entities.add(entity);
    604                }
    605            }
    606
    607        }
    608    }
    609
    610    /**
    611     * Calculates the stacked value of the all series up to, but not including
    612     * <code>series</code> for the specified item. It returns 0.0 if
    613     * <code>series</code> is the first series, i.e. 0.
    614     *
    615     * @param dataset  the dataset.
    616     * @param series  the series.
    617     * @param index  the index.
    618     *
    619     * @return The cumulative value for all series' values up to but excluding
    620     *         <code>series</code> for <code>index</code>.
    621     */
    622    protected double getPreviousHeight(TableXYDataset dataset,
    623                                       int series, int index) {
    624        double result = 0.0;
    625        for (int i = 0; i < series; i++) {
    626            double value = dataset.getYValue(i, index);
    627            if (!Double.isNaN(value)) {
    628                result += value;
    629            }
    630        }
    631        return result;
    632    }
    633
    634    /**
    635     * Tests the renderer for equality with an arbitrary object.
    636     *
    637     * @param obj  the object (<code>null</code> permitted).
    638     *
    639     * @return A boolean.
    640     */
    641    public boolean equals(Object obj) {
    642        if (obj == this) {
    643            return true;
    644        }
    645        if (!(obj instanceof StackedXYAreaRenderer) || !super.equals(obj)) {
    646            return false;
    647        }
    648        StackedXYAreaRenderer that = (StackedXYAreaRenderer) obj;
    649        if (!PaintUtilities.equal(this.shapePaint, that.shapePaint)) {
    650            return false;
    651        }
    652        if (!ObjectUtilities.equal(this.shapeStroke, that.shapeStroke)) {
    653            return false;
    654        }
    655        return true;
    656    }
    657
    658    /**
    659     * Returns a clone of the renderer.
    660     *
    661     * @return A clone.
    662     *
    663     * @throws CloneNotSupportedException if the renderer cannot be cloned.
    664     */
    665    public Object clone() throws CloneNotSupportedException {
    666        return super.clone();
    667    }
    668
    669    /**
    670     * Provides serialization support.
    671     *
    672     * @param stream  the input stream.
    673     *
    674     * @throws IOException  if there is an I/O error.
    675     * @throws ClassNotFoundException  if there is a classpath problem.
    676     */
    677    private void readObject(ObjectInputStream stream)
    678            throws IOException, ClassNotFoundException {
    679        stream.defaultReadObject();
    680        this.shapePaint = SerialUtilities.readPaint(stream);
    681        this.shapeStroke = SerialUtilities.readStroke(stream);
    682    }
    683
    684    /**
    685     * Provides serialization support.
    686     *
    687     * @param stream  the output stream.
    688     *
    689     * @throws IOException  if there is an I/O error.
    690     */
    691    private void writeObject(ObjectOutputStream stream) throws IOException {
    692        stream.defaultWriteObject();
    693        SerialUtilities.writePaint(this.shapePaint, stream);
    694        SerialUtilities.writeStroke(this.shapeStroke, stream);
    695    }
    696
    697}