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