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