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