    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * --------------------------
    028     * 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
    069    package org.jfree.chart.renderer.xy;
    070
    071    import java.awt.Graphics2D;
    072    import java.awt.Paint;
    073    import java.awt.Point;
    074    import java.awt.Polygon;
    075    import java.awt.Shape;
    076    import java.awt.Stroke;
    077    import java.awt.geom.Line2D;
    078    import java.awt.geom.Rectangle2D;
    079    import java.io.IOException;
    080    import java.io.ObjectInputStream;
    081    import java.io.ObjectOutputStream;
    082    import java.io.Serializable;
    083    import java.util.Stack;
    084
    085    import org.jfree.chart.axis.ValueAxis;
    086    import org.jfree.chart.entity.EntityCollection;
    087    import org.jfree.chart.entity.XYItemEntity;
    088    import org.jfree.chart.event.RendererChangeEvent;
    089    import org.jfree.chart.labels.XYToolTipGenerator;
    090    import org.jfree.chart.plot.CrosshairState;
    091    import org.jfree.chart.plot.PlotOrientation;
    092    import org.jfree.chart.plot.PlotRenderingInfo;
    093    import org.jfree.chart.plot.XYPlot;
    094    import org.jfree.chart.urls.XYURLGenerator;
    095    import org.jfree.data.Range;
    096    import org.jfree.data.general.DatasetUtilities;
    097    import org.jfree.data.xy.TableXYDataset;
    098    import org.jfree.data.xy.XYDataset;
    099    import org.jfree.io.SerialUtilities;
    100    import org.jfree.util.ObjectUtilities;
    101    import org.jfree.util.PaintUtilities;
    102    import org.jfree.util.PublicCloneable;
    103    import org.jfree.util.ShapeUtilities;
    104
    105    /**
    106     * A stacked area renderer for the {@link XYPlot} class.
    107     * <br><br>
    108     * SPECIAL NOTE:  This renderer does not currently handle negative data values
    109     * correctly.  This should get fixed at some point, but the current workaround
    110     * is to use the {@link StackedXYAreaRenderer2} class instead.
    111     */
    112    public class StackedXYAreaRenderer extends XYAreaRenderer
    113            implements Cloneable, PublicCloneable, Serializable {
    114
    115        /** For serialization. */
    116        private static final long serialVersionUID = 5217394318178570889L;
    117
    118         /**
    119         * A state object for use by this renderer.
    120         */
    121        static class StackedXYAreaRendererState extends XYItemRendererState {
    122
    123            /** The area for the current series. */
    124            private Polygon seriesArea;
    125
    126            /** The line. */
    127            private Line2D line;
    128
    129            /** The points from the last series. */
    130            private Stack lastSeriesPoints;
    131
    132            /** The points for the current series. */
    133            private Stack currentSeriesPoints;
    134
    135            /**
    136             * Creates a new state for the renderer.
    137             *
    138             * @param info  the plot rendering info.
    139             */
    140            public StackedXYAreaRendererState(PlotRenderingInfo info) {
    141                super(info);
    142                this.seriesArea = null;
    143                this.line = new Line2D.Double();
    144                this.lastSeriesPoints = new Stack();
    145                this.currentSeriesPoints = new Stack();
    146            }
    147
    148            /**
    149             * Returns the series area.
    150             *
    151             * @return The series area.
    152             */
    153            public Polygon getSeriesArea() {
    154                return this.seriesArea;
    155            }
    156
    157            /**
    158             * Sets the series area.
    159             *
    160             * @param area  the area.
    161             */
    162            public void setSeriesArea(Polygon area) {
    163                this.seriesArea = area;
    164            }
    165
    166            /**
    167             * Returns the working line.
    168             *
    169             * @return The working line.
    170             */
    171            public Line2D getLine() {
    172                return this.line;
    173            }
    174
    175            /**
    176             * Returns the current series points.
    177             *
    178             * @return The current series points.
    179             */
    180            public Stack getCurrentSeriesPoints() {
    181                return this.currentSeriesPoints;
    182            }
    183
    184            /**
    185             * Sets the current series points.
    186             *
    187             * @param points  the points.
    188             */
    189            public void setCurrentSeriesPoints(Stack points) {
    190                this.currentSeriesPoints = points;
    191            }
    192
    193            /**
    194             * Returns the last series points.
    195             *
    196             * @return The last series points.
    197             */
    198            public Stack getLastSeriesPoints() {
    199                return this.lastSeriesPoints;
    200            }
    201
    202            /**
    203             * Sets the last series points.
    204             *
    205             * @param points  the points.
    206             */
    207            public void setLastSeriesPoints(Stack points) {
    208                this.lastSeriesPoints = points;
    209            }
    210
    211        }
    212
    213        /**
    214         * Custom Paint for drawing all shapes, if null defaults to series shapes
    215         */
    216        private transient Paint shapePaint = null;
    217
    218        /**
    219         * Custom Stroke for drawing all shapes, if null defaults to series
    220         * strokes.
    221         */
    222        private transient Stroke shapeStroke = null;
    223
    224        /**
    225         * Creates a new renderer.
    226         */
    227        public StackedXYAreaRenderer() {
    228            this(AREA);
    229        }
    230
    231        /**
    232         * Constructs a new renderer.
    233         *
    234         * @param type  the type of the renderer.
    235         */
    236        public StackedXYAreaRenderer(int type) {
    237            this(type, null, null);
    238        }
    239
    240        /**
    241         * Constructs a new renderer.  To specify the type of renderer, use one of
    242         * the constants: <code>SHAPES</code>, <code>LINES</code>,
    243         * <code>SHAPES_AND_LINES</code>, <code>AREA</code> or
    244         * <code>AREA_AND_SHAPES</code>.
    245         *
    246         * @param type  the type of renderer.
    247         * @param labelGenerator  the tool tip generator to use (<code>null</code>
    248         *                        is none).
    249         * @param urlGenerator  the URL generator (<code>null</code> permitted).
    250         */
    251        public StackedXYAreaRenderer(int type,
    252                                     XYToolTipGenerator labelGenerator,
    253                                     XYURLGenerator urlGenerator) {
    254
    255            super(type, labelGenerator, urlGenerator);
    256        }
    257
    258        /**
    259         * Returns the paint used for rendering shapes, or <code>null</code> if
    260         * using series paints.
    261         *
    262         * @return The paint (possibly <code>null</code>).
    263         *
    264         * @see #setShapePaint(Paint)
    265         */
    266        public Paint getShapePaint() {
    267            return this.shapePaint;
    268        }
    269
    270        /**
    271         * Sets the paint for rendering shapes and sends a
    272         * {@link RendererChangeEvent} to all registered listeners.
    273         *
    274         * @param shapePaint  the paint (<code>null</code> permitted).
    275         *
    276         * @see #getShapePaint()
    277         */
    278        public void setShapePaint(Paint shapePaint) {
    279            this.shapePaint = shapePaint;
    280            fireChangeEvent();
    281        }
    282
    283        /**
    284         * Returns the stroke used for rendering shapes, or <code>null</code> if
    285         * using series strokes.
    286         *
    287         * @return The stroke (possibly <code>null</code>).
    288         *
    289         * @see #setShapeStroke(Stroke)
    290         */
    291        public Stroke getShapeStroke() {
    292            return this.shapeStroke;
    293        }
    294
    295        /**
    296         * Sets the stroke for rendering shapes and sends a
    297         * {@link RendererChangeEvent} to all registered listeners.
    298         *
    299         * @param shapeStroke  the stroke (<code>null</code> permitted).
    300         *
    301         * @see #getShapeStroke()
    302         */
    303        public void setShapeStroke(Stroke shapeStroke) {
    304            this.shapeStroke = shapeStroke;
    305            fireChangeEvent();
    306        }
    307
    308        /**
    309         * Initialises the renderer. This method will be called before the first
    310         * item is rendered, giving the renderer an opportunity to initialise any
    311         * state information it wants to maintain.
    312         *
    313         * @param g2  the graphics device.
    314         * @param dataArea  the area inside the axes.
    315         * @param plot  the plot.
    316         * @param data  the data.
    317         * @param info  an optional info collection object to return data back to
    318         *              the caller.
    319         *
    320         * @return A state object that should be passed to subsequent calls to the
    321         *         drawItem() method.
    322         */
    323        public XYItemRendererState initialise(Graphics2D g2,
    324                                              Rectangle2D dataArea,
    325                                              XYPlot plot,
    326                                              XYDataset data,
    327                                              PlotRenderingInfo info) {
    328
    329            XYItemRendererState state = new StackedXYAreaRendererState(info);
    330            // in the rendering process, there is special handling for item
    331            // zero, so we can't support processing of visible data items only
    332            state.setProcessVisibleItemsOnly(false);
    333            return state;
    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(lookupSeriesOutlineStroke(series));
    526                        g2.setPaint(lookupSeriesOutlinePaint(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    }