Source for org.jfree.chart.renderer.xy.StackedXYAreaRenderer

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