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