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

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, 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:  * XYLineAndShapeRenderer.java
  29:  * ---------------------------
  30:  * (C) Copyright 2004-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: XYLineAndShapeRenderer.java,v 1.20.2.9 2007/02/21 11:49:46 mungady Exp $
  36:  *
  37:  * Changes:
  38:  * --------
  39:  * 27-Jan-2004 : Version 1 (DG);
  40:  * 10-Feb-2004 : Minor change to drawItem() method to make cut-and-paste 
  41:  *               overriding easier (DG);
  42:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
  43:  * 25-Aug-2004 : Added support for chart entities (required for tooltips) (DG);
  44:  * 24-Sep-2004 : Added flag to allow whole series to be drawn as a path 
  45:  *               (necessary when using a dashed stroke with many data 
  46:  *               items) (DG);
  47:  * 04-Oct-2004 : Renamed BooleanUtils --> BooleanUtilities (DG);
  48:  * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  49:  * 27-Jan-2005 : The getLegendItem() method now omits hidden series (DG);
  50:  * 28-Jan-2005 : Added new constructor (DG);
  51:  * 09-Mar-2005 : Added fillPaint settings (DG);
  52:  * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
  53:  * 22-Jul-2005 : Renamed defaultLinesVisible --> baseLinesVisible, 
  54:  *               defaultShapesVisible --> baseShapesVisible and
  55:  *               defaultShapesFilled --> baseShapesFilled (DG);
  56:  * 29-Jul-2005 : Added code to draw item labels (DG);
  57:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  58:  * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
  59:  * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
  60:  * 21-Feb-2007 : Fixed bugs in clone() and equals() (DG);
  61:  *
  62:  */
  63: 
  64: package org.jfree.chart.renderer.xy;
  65: 
  66: import java.awt.Graphics2D;
  67: import java.awt.Paint;
  68: import java.awt.Shape;
  69: import java.awt.Stroke;
  70: import java.awt.geom.GeneralPath;
  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: 
  78: import org.jfree.chart.LegendItem;
  79: import org.jfree.chart.axis.ValueAxis;
  80: import org.jfree.chart.entity.EntityCollection;
  81: import org.jfree.chart.event.RendererChangeEvent;
  82: import org.jfree.chart.plot.CrosshairState;
  83: import org.jfree.chart.plot.PlotOrientation;
  84: import org.jfree.chart.plot.PlotRenderingInfo;
  85: import org.jfree.chart.plot.XYPlot;
  86: import org.jfree.data.xy.XYDataset;
  87: import org.jfree.io.SerialUtilities;
  88: import org.jfree.ui.RectangleEdge;
  89: import org.jfree.util.BooleanList;
  90: import org.jfree.util.BooleanUtilities;
  91: import org.jfree.util.ObjectUtilities;
  92: import org.jfree.util.PublicCloneable;
  93: import org.jfree.util.ShapeUtilities;
  94: 
  95: /**
  96:  * A renderer that can be used with the {@link XYPlot} class.
  97:  */
  98: public class XYLineAndShapeRenderer extends AbstractXYItemRenderer 
  99:                                     implements XYItemRenderer, 
 100:                                                Cloneable,
 101:                                                PublicCloneable,
 102:                                                Serializable {
 103: 
 104:     /** For serialization. */
 105:     private static final long serialVersionUID = -7435246895986425885L;
 106:     
 107:     /** A flag that controls whether or not lines are visible for ALL series. */
 108:     private Boolean linesVisible;
 109: 
 110:     /** 
 111:      * A table of flags that control (per series) whether or not lines are 
 112:      * visible. 
 113:      */
 114:     private BooleanList seriesLinesVisible;
 115: 
 116:     /** The default value returned by the getLinesVisible() method. */
 117:     private boolean baseLinesVisible;
 118: 
 119:     /** The shape that is used to represent a line in the legend. */
 120:     private transient Shape legendLine;
 121:     
 122:     /** 
 123:      * A flag that controls whether or not shapes are visible for ALL series. 
 124:      */
 125:     private Boolean shapesVisible;
 126: 
 127:     /** 
 128:      * A table of flags that control (per series) whether or not shapes are 
 129:      * visible. 
 130:      */
 131:     private BooleanList seriesShapesVisible;
 132: 
 133:     /** The default value returned by the getShapeVisible() method. */
 134:     private boolean baseShapesVisible;
 135: 
 136:     /** A flag that controls whether or not shapes are filled for ALL series. */
 137:     private Boolean shapesFilled;
 138: 
 139:     /** 
 140:      * A table of flags that control (per series) whether or not shapes are 
 141:      * filled. 
 142:      */
 143:     private BooleanList seriesShapesFilled;
 144: 
 145:     /** The default value returned by the getShapeFilled() method. */
 146:     private boolean baseShapesFilled;
 147:     
 148:     /** A flag that controls whether outlines are drawn for shapes. */
 149:     private boolean drawOutlines;
 150:     
 151:     /** 
 152:      * A flag that controls whether the fill paint is used for filling 
 153:      * shapes. 
 154:      */
 155:     private boolean useFillPaint;
 156:     
 157:     /** 
 158:      * A flag that controls whether the outline paint is used for drawing shape 
 159:      * outlines. 
 160:      */
 161:     private boolean useOutlinePaint;
 162:     
 163:     /** 
 164:      * A flag that controls whether or not each series is drawn as a single 
 165:      * path. 
 166:      */
 167:     private boolean drawSeriesLineAsPath;
 168: 
 169:     /**
 170:      * Creates a new renderer with both lines and shapes visible.
 171:      */
 172:     public XYLineAndShapeRenderer() {
 173:         this(true, true);
 174:     }
 175:     
 176:     /**
 177:      * Creates a new renderer.
 178:      * 
 179:      * @param lines  lines visible?
 180:      * @param shapes  shapes visible?
 181:      */
 182:     public XYLineAndShapeRenderer(boolean lines, boolean shapes) {
 183:         this.linesVisible = null;
 184:         this.seriesLinesVisible = new BooleanList();
 185:         this.baseLinesVisible = lines;
 186:         this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
 187:         
 188:         this.shapesVisible = null;
 189:         this.seriesShapesVisible = new BooleanList();
 190:         this.baseShapesVisible = shapes;
 191:         
 192:         this.shapesFilled = null;
 193:         this.useFillPaint = false;     // use item paint for fills by default
 194:         this.seriesShapesFilled = new BooleanList();
 195:         this.baseShapesFilled = true;
 196: 
 197:         this.drawOutlines = true;     
 198:         this.useOutlinePaint = false;  // use item paint for outlines by 
 199:                                        // default, not outline paint
 200:         
 201:         this.drawSeriesLineAsPath = false;
 202:     }
 203:     
 204:     /**
 205:      * Returns a flag that controls whether or not each series is drawn as a 
 206:      * single path.
 207:      * 
 208:      * @return A boolean.
 209:      * 
 210:      * @see #setDrawSeriesLineAsPath(boolean)
 211:      */
 212:     public boolean getDrawSeriesLineAsPath() {
 213:         return this.drawSeriesLineAsPath;
 214:     }
 215:     
 216:     /**
 217:      * Sets the flag that controls whether or not each series is drawn as a 
 218:      * single path.
 219:      * 
 220:      * @param flag  the flag.
 221:      * 
 222:      * @see #getDrawSeriesLineAsPath()
 223:      */
 224:     public void setDrawSeriesLineAsPath(boolean flag) {
 225:         if (this.drawSeriesLineAsPath != flag) {
 226:             this.drawSeriesLineAsPath = flag;
 227:             notifyListeners(new RendererChangeEvent(this));
 228:         }
 229:     }
 230:     
 231:     /**
 232:      * Returns the number of passes through the data that the renderer requires 
 233:      * in order to draw the chart.  Most charts will require a single pass, but 
 234:      * some require two passes.
 235:      * 
 236:      * @return The pass count.
 237:      */
 238:     public int getPassCount() {
 239:         return 2;
 240:     }
 241:     
 242:     // LINES VISIBLE
 243: 
 244:     /**
 245:      * Returns the flag used to control whether or not the shape for an item is 
 246:      * visible.
 247:      *
 248:      * @param series  the series index (zero-based).
 249:      * @param item  the item index (zero-based).
 250:      *
 251:      * @return A boolean.
 252:      */
 253:     public boolean getItemLineVisible(int series, int item) {
 254:         Boolean flag = this.linesVisible;
 255:         if (flag == null) {
 256:             flag = getSeriesLinesVisible(series);
 257:         }
 258:         if (flag != null) {
 259:             return flag.booleanValue();
 260:         }
 261:         else {
 262:             return this.baseLinesVisible;   
 263:         }
 264:     }
 265: 
 266:     /**
 267:      * Returns a flag that controls whether or not lines are drawn for ALL 
 268:      * series.  If this flag is <code>null</code>, then the "per series" 
 269:      * settings will apply.
 270:      * 
 271:      * @return A flag (possibly <code>null</code>).
 272:      * 
 273:      * @see #setLinesVisible(Boolean) 
 274:      */
 275:     public Boolean getLinesVisible() {
 276:         return this.linesVisible;   
 277:     }
 278:     
 279:     /**
 280:      * Sets a flag that controls whether or not lines are drawn between the 
 281:      * items in ALL series, and sends a {@link RendererChangeEvent} to all 
 282:      * registered listeners.  You need to set this to <code>null</code> if you 
 283:      * want the "per series" settings to apply.
 284:      *
 285:      * @param visible  the flag (<code>null</code> permitted).
 286:      * 
 287:      * @see #getLinesVisible()
 288:      */
 289:     public void setLinesVisible(Boolean visible) {
 290:         this.linesVisible = visible;
 291:         notifyListeners(new RendererChangeEvent(this));
 292:     }
 293: 
 294:     /**
 295:      * Sets a flag that controls whether or not lines are drawn between the 
 296:      * items in ALL series, and sends a {@link RendererChangeEvent} to all 
 297:      * registered listeners.
 298:      *
 299:      * @param visible  the flag.
 300:      * 
 301:      * @see #getLinesVisible()
 302:      */
 303:     public void setLinesVisible(boolean visible) {
 304:         // we use BooleanUtilities here to preserve JRE 1.3.1 compatibility
 305:         setLinesVisible(BooleanUtilities.valueOf(visible));
 306:     }
 307: 
 308:     /**
 309:      * Returns the flag used to control whether or not the lines for a series 
 310:      * are visible.
 311:      *
 312:      * @param series  the series index (zero-based).
 313:      *
 314:      * @return The flag (possibly <code>null</code>).
 315:      * 
 316:      * @see #setSeriesLinesVisible(int, Boolean)
 317:      */
 318:     public Boolean getSeriesLinesVisible(int series) {
 319:         return this.seriesLinesVisible.getBoolean(series);
 320:     }
 321: 
 322:     /**
 323:      * Sets the 'lines visible' flag for a series and sends a 
 324:      * {@link RendererChangeEvent} to all registered listeners.
 325:      *
 326:      * @param series  the series index (zero-based).
 327:      * @param flag  the flag (<code>null</code> permitted).
 328:      * 
 329:      * @see #getSeriesLinesVisible(int)
 330:      */
 331:     public void setSeriesLinesVisible(int series, Boolean flag) {
 332:         this.seriesLinesVisible.setBoolean(series, flag);
 333:         notifyListeners(new RendererChangeEvent(this));
 334:     }
 335: 
 336:     /**
 337:      * Sets the 'lines visible' flag for a series and sends a 
 338:      * {@link RendererChangeEvent} to all registered listeners.
 339:      * 
 340:      * @param series  the series index (zero-based).
 341:      * @param visible  the flag.
 342:      * 
 343:      * @see #getSeriesLinesVisible(int)
 344:      */
 345:     public void setSeriesLinesVisible(int series, boolean visible) {
 346:         setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
 347:     }
 348:     
 349:     /**
 350:      * Returns the base 'lines visible' attribute.
 351:      *
 352:      * @return The base flag.
 353:      * 
 354:      * @see #setBaseLinesVisible(boolean)
 355:      */
 356:     public boolean getBaseLinesVisible() {
 357:         return this.baseLinesVisible;
 358:     }
 359: 
 360:     /**
 361:      * Sets the base 'lines visible' flag and sends a 
 362:      * {@link RendererChangeEvent} to all registered listeners.
 363:      *
 364:      * @param flag  the flag.
 365:      * 
 366:      * @see #getBaseLinesVisible()
 367:      */
 368:     public void setBaseLinesVisible(boolean flag) {
 369:         this.baseLinesVisible = flag;
 370:         notifyListeners(new RendererChangeEvent(this));
 371:     }
 372: 
 373:     /**
 374:      * Returns the shape used to represent a line in the legend.
 375:      * 
 376:      * @return The legend line (never <code>null</code>).
 377:      * 
 378:      * @see #setLegendLine(Shape)
 379:      */
 380:     public Shape getLegendLine() {
 381:         return this.legendLine;   
 382:     }
 383:     
 384:     /**
 385:      * Sets the shape used as a line in each legend item and sends a 
 386:      * {@link RendererChangeEvent} to all registered listeners.
 387:      * 
 388:      * @param line  the line (<code>null</code> not permitted).
 389:      * 
 390:      * @see #getLegendLine()
 391:      */
 392:     public void setLegendLine(Shape line) {
 393:         if (line == null) {
 394:             throw new IllegalArgumentException("Null 'line' argument.");   
 395:         }
 396:         this.legendLine = line;
 397:         notifyListeners(new RendererChangeEvent(this));
 398:     }
 399: 
 400:     // SHAPES VISIBLE
 401: 
 402:     /**
 403:      * Returns the flag used to control whether or not the shape for an item is
 404:      * visible.
 405:      * <p>
 406:      * The default implementation passes control to the 
 407:      * <code>getSeriesShapesVisible</code> method. You can override this method
 408:      * if you require different behaviour.
 409:      *
 410:      * @param series  the series index (zero-based).
 411:      * @param item  the item index (zero-based).
 412:      *
 413:      * @return A boolean.
 414:      */
 415:     public boolean getItemShapeVisible(int series, int item) {
 416:         Boolean flag = this.shapesVisible;
 417:         if (flag == null) {
 418:             flag = getSeriesShapesVisible(series);
 419:         }
 420:         if (flag != null) {
 421:             return flag.booleanValue();   
 422:         }
 423:         else {
 424:             return this.baseShapesVisible;
 425:         }
 426:     }
 427: 
 428:     /**
 429:      * Returns the flag that controls whether the shapes are visible for the 
 430:      * items in ALL series.
 431:      * 
 432:      * @return The flag (possibly <code>null</code>).
 433:      * 
 434:      * @see #setShapesVisible(Boolean)
 435:      */
 436:     public Boolean getShapesVisible() {
 437:         return this.shapesVisible;    
 438:     }
 439:     
 440:     /**
 441:      * Sets the 'shapes visible' for ALL series and sends a 
 442:      * {@link RendererChangeEvent} to all registered listeners.
 443:      *
 444:      * @param visible  the flag (<code>null</code> permitted).
 445:      * 
 446:      * @see #getShapesVisible()
 447:      */
 448:     public void setShapesVisible(Boolean visible) {
 449:         this.shapesVisible = visible;
 450:         notifyListeners(new RendererChangeEvent(this));
 451:     }
 452: 
 453:     /**
 454:      * Sets the 'shapes visible' for ALL series and sends a 
 455:      * {@link RendererChangeEvent} to all registered listeners.
 456:      * 
 457:      * @param visible  the flag.
 458:      * 
 459:      * @see #getShapesVisible()
 460:      */
 461:     public void setShapesVisible(boolean visible) {
 462:         setShapesVisible(BooleanUtilities.valueOf(visible));
 463:     }
 464: 
 465:     /**
 466:      * Returns the flag used to control whether or not the shapes for a series
 467:      * are visible.
 468:      *
 469:      * @param series  the series index (zero-based).
 470:      *
 471:      * @return A boolean.
 472:      * 
 473:      * @see #setSeriesShapesVisible(int, Boolean)
 474:      */
 475:     public Boolean getSeriesShapesVisible(int series) {
 476:         return this.seriesShapesVisible.getBoolean(series);
 477:     }
 478: 
 479:     /**
 480:      * Sets the 'shapes visible' flag for a series and sends a 
 481:      * {@link RendererChangeEvent} to all registered listeners.
 482:      * 
 483:      * @param series  the series index (zero-based).
 484:      * @param visible  the flag.
 485:      * 
 486:      * @see #getSeriesShapesVisible(int)
 487:      */
 488:     public void setSeriesShapesVisible(int series, boolean visible) {
 489:         setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
 490:     }
 491:     
 492:     /**
 493:      * Sets the 'shapes visible' flag for a series and sends a 
 494:      * {@link RendererChangeEvent} to all registered listeners.
 495:      *
 496:      * @param series  the series index (zero-based).
 497:      * @param flag  the flag.
 498:      * 
 499:      * @see #getSeriesShapesVisible(int)
 500:      */
 501:     public void setSeriesShapesVisible(int series, Boolean flag) {
 502:         this.seriesShapesVisible.setBoolean(series, flag);
 503:         notifyListeners(new RendererChangeEvent(this));
 504:     }
 505: 
 506:     /**
 507:      * Returns the base 'shape visible' attribute.
 508:      *
 509:      * @return The base flag.
 510:      * 
 511:      * @see #setBaseShapesVisible(boolean)
 512:      */
 513:     public boolean getBaseShapesVisible() {
 514:         return this.baseShapesVisible;
 515:     }
 516: 
 517:     /**
 518:      * Sets the base 'shapes visible' flag and sends a 
 519:      * {@link RendererChangeEvent} to all registered listeners.
 520:      *
 521:      * @param flag  the flag.
 522:      * 
 523:      * @see #getBaseShapesVisible()
 524:      */
 525:     public void setBaseShapesVisible(boolean flag) {
 526:         this.baseShapesVisible = flag;
 527:         notifyListeners(new RendererChangeEvent(this));
 528:     }
 529: 
 530:     // SHAPES FILLED
 531: 
 532:     /**
 533:      * Returns the flag used to control whether or not the shape for an item 
 534:      * is filled.
 535:      * <p>
 536:      * The default implementation passes control to the 
 537:      * <code>getSeriesShapesFilled</code> method. You can override this method
 538:      * if you require different behaviour.
 539:      *
 540:      * @param series  the series index (zero-based).
 541:      * @param item  the item index (zero-based).
 542:      *
 543:      * @return A boolean.
 544:      */
 545:     public boolean getItemShapeFilled(int series, int item) {
 546:         Boolean flag = this.shapesFilled;
 547:         if (flag == null) {
 548:             flag = getSeriesShapesFilled(series);
 549:         }
 550:         if (flag != null) {
 551:             return flag.booleanValue();   
 552:         }
 553:         else {
 554:             return this.baseShapesFilled;   
 555:         }
 556:     }
 557: 
 558:     // FIXME: Why no getShapesFilled()?  An oversight probably
 559:     
 560:     /**
 561:      * Sets the 'shapes filled' for ALL series and sends a 
 562:      * {@link RendererChangeEvent} to all registered listeners.
 563:      *
 564:      * @param filled  the flag.
 565:      */
 566:     public void setShapesFilled(boolean filled) {
 567:         setShapesFilled(BooleanUtilities.valueOf(filled));
 568:     }
 569: 
 570:     /**
 571:      * Sets the 'shapes filled' for ALL series and sends a 
 572:      * {@link RendererChangeEvent} to all registered listeners.
 573:      *
 574:      * @param filled  the flag (<code>null</code> permitted).
 575:      */
 576:     public void setShapesFilled(Boolean filled) {
 577:         this.shapesFilled = filled;
 578:         notifyListeners(new RendererChangeEvent(this));
 579:     }
 580:     
 581:     /**
 582:      * Returns the flag used to control whether or not the shapes for a series
 583:      * are filled.
 584:      *
 585:      * @param series  the series index (zero-based).
 586:      *
 587:      * @return A boolean.
 588:      * 
 589:      * @see #setSeriesShapesFilled(int, Boolean)
 590:      */
 591:     public Boolean getSeriesShapesFilled(int series) {
 592:         return this.seriesShapesFilled.getBoolean(series);
 593:     }
 594: 
 595:     /**
 596:      * Sets the 'shapes filled' flag for a series and sends a 
 597:      * {@link RendererChangeEvent} to all registered listeners.
 598:      *
 599:      * @param series  the series index (zero-based).
 600:      * @param flag  the flag.
 601:      * 
 602:      * @see #getSeriesShapesFilled(int)
 603:      */
 604:     public void setSeriesShapesFilled(int series, boolean flag) {
 605:         setSeriesShapesFilled(series, BooleanUtilities.valueOf(flag));
 606:     }
 607: 
 608:     /**
 609:      * Sets the 'shapes filled' flag for a series and sends a 
 610:      * {@link RendererChangeEvent} to all registered listeners.
 611:      *
 612:      * @param series  the series index (zero-based).
 613:      * @param flag  the flag.
 614:      * 
 615:      * @see #getSeriesShapesFilled(int)
 616:      */
 617:     public void setSeriesShapesFilled(int series, Boolean flag) {
 618:         this.seriesShapesFilled.setBoolean(series, flag);
 619:         notifyListeners(new RendererChangeEvent(this));
 620:     }
 621: 
 622:     /**
 623:      * Returns the base 'shape filled' attribute.
 624:      *
 625:      * @return The base flag.
 626:      * 
 627:      * @see #setBaseShapesFilled(boolean)
 628:      */
 629:     public boolean getBaseShapesFilled() {
 630:         return this.baseShapesFilled;
 631:     }
 632: 
 633:     /**
 634:      * Sets the base 'shapes filled' flag and sends a 
 635:      * {@link RendererChangeEvent} to all registered listeners.
 636:      *
 637:      * @param flag  the flag.
 638:      * 
 639:      * @see #getBaseShapesFilled()
 640:      */
 641:     public void setBaseShapesFilled(boolean flag) {
 642:         this.baseShapesFilled = flag;
 643:         notifyListeners(new RendererChangeEvent(this));
 644:     }
 645: 
 646:     /**
 647:      * Returns <code>true</code> if outlines should be drawn for shapes, and 
 648:      * <code>false</code> otherwise.
 649:      * 
 650:      * @return A boolean.
 651:      * 
 652:      * @see #setDrawOutlines(boolean)
 653:      */
 654:     public boolean getDrawOutlines() {
 655:         return this.drawOutlines;
 656:     }
 657:     
 658:     /**
 659:      * Sets the flag that controls whether outlines are drawn for 
 660:      * shapes, and sends a {@link RendererChangeEvent} to all registered 
 661:      * listeners. 
 662:      * <P>
 663:      * In some cases, shapes look better if they do NOT have an outline, but 
 664:      * this flag allows you to set your own preference.
 665:      * 
 666:      * @param flag  the flag.
 667:      * 
 668:      * @see #getDrawOutlines()
 669:      */
 670:     public void setDrawOutlines(boolean flag) {
 671:         this.drawOutlines = flag;
 672:         notifyListeners(new RendererChangeEvent(this));
 673:     }
 674:     
 675:     /**
 676:      * Returns <code>true</code> if the renderer should use the fill paint 
 677:      * setting to fill shapes, and <code>false</code> if it should just
 678:      * use the regular paint.
 679:      * 
 680:      * @return A boolean.
 681:      * 
 682:      * @see #setUseFillPaint(boolean)
 683:      * @see #getUseOutlinePaint()
 684:      */
 685:     public boolean getUseFillPaint() {
 686:         return this.useFillPaint;
 687:     }
 688:     
 689:     /**
 690:      * Sets the flag that controls whether the fill paint is used to fill 
 691:      * shapes, and sends a {@link RendererChangeEvent} to all 
 692:      * registered listeners.
 693:      * 
 694:      * @param flag  the flag.
 695:      * 
 696:      * @see #getUseFillPaint()
 697:      */
 698:     public void setUseFillPaint(boolean flag) {
 699:         this.useFillPaint = flag;
 700:         notifyListeners(new RendererChangeEvent(this));
 701:     }
 702:     
 703:     /**
 704:      * Returns <code>true</code> if the renderer should use the outline paint 
 705:      * setting to draw shape outlines, and <code>false</code> if it should just
 706:      * use the regular paint.
 707:      * 
 708:      * @return A boolean.
 709:      * 
 710:      * @see #setUseOutlinePaint(boolean)
 711:      * @see #getUseFillPaint()
 712:      */
 713:     public boolean getUseOutlinePaint() {
 714:         return this.useOutlinePaint;
 715:     }
 716:     
 717:     /**
 718:      * Sets the flag that controls whether the outline paint is used to draw 
 719:      * shape outlines, and sends a {@link RendererChangeEvent} to all 
 720:      * registered listeners.
 721:      * 
 722:      * @param flag  the flag.
 723:      * 
 724:      * @see #getUseOutlinePaint()
 725:      */
 726:     public void setUseOutlinePaint(boolean flag) {
 727:         this.useOutlinePaint = flag;
 728:         notifyListeners(new RendererChangeEvent(this));
 729:     }
 730:     
 731:     /**
 732:      * Records the state for the renderer.  This is used to preserve state 
 733:      * information between calls to the drawItem() method for a single chart 
 734:      * drawing.
 735:      */
 736:     public static class State extends XYItemRendererState {
 737:         
 738:         /** The path for the current series. */
 739:         public GeneralPath seriesPath;
 740:         
 741:         /** 
 742:          * A flag that indicates if the last (x, y) point was 'good' 
 743:          * (non-null). 
 744:          */
 745:         private boolean lastPointGood;
 746:         
 747:         /**
 748:          * Creates a new state instance.
 749:          * 
 750:          * @param info  the plot rendering info.
 751:          */
 752:         public State(PlotRenderingInfo info) {
 753:             super(info);
 754:         }
 755:         
 756:         /**
 757:          * Returns a flag that indicates if the last point drawn (in the 
 758:          * current series) was 'good' (non-null).
 759:          * 
 760:          * @return A boolean.
 761:          */
 762:         public boolean isLastPointGood() {
 763:             return this.lastPointGood;
 764:         }
 765:         
 766:         /**
 767:          * Sets a flag that indicates if the last point drawn (in the current 
 768:          * series) was 'good' (non-null).
 769:          * 
 770:          * @param good  the flag.
 771:          */
 772:         public void setLastPointGood(boolean good) {
 773:             this.lastPointGood = good;
 774:         }
 775:     }
 776:     
 777:     /**
 778:      * Initialises the renderer.
 779:      * <P>
 780:      * This method will be called before the first item is rendered, giving the
 781:      * renderer an opportunity to initialise any state information it wants to 
 782:      * maintain.  The renderer can do nothing if it chooses.
 783:      *
 784:      * @param g2  the graphics device.
 785:      * @param dataArea  the area inside the axes.
 786:      * @param plot  the plot.
 787:      * @param data  the data.
 788:      * @param info  an optional info collection object to return data back to 
 789:      *              the caller.
 790:      *
 791:      * @return The renderer state.
 792:      */
 793:     public XYItemRendererState initialise(Graphics2D g2,
 794:                                           Rectangle2D dataArea,
 795:                                           XYPlot plot,
 796:                                           XYDataset data,
 797:                                           PlotRenderingInfo info) {
 798: 
 799:         State state = new State(info);
 800:         state.seriesPath = new GeneralPath();
 801:         return state;
 802: 
 803:     }
 804:     
 805:     /**
 806:      * Draws the visual representation of a single data item.
 807:      *
 808:      * @param g2  the graphics device.
 809:      * @param state  the renderer state.
 810:      * @param dataArea  the area within which the data is being drawn.
 811:      * @param info  collects information about the drawing.
 812:      * @param plot  the plot (can be used to obtain standard color 
 813:      *              information etc).
 814:      * @param domainAxis  the domain axis.
 815:      * @param rangeAxis  the range axis.
 816:      * @param dataset  the dataset.
 817:      * @param series  the series index (zero-based).
 818:      * @param item  the item index (zero-based).
 819:      * @param crosshairState  crosshair information for the plot 
 820:      *                        (<code>null</code> permitted).
 821:      * @param pass  the pass index.
 822:      */
 823:     public void drawItem(Graphics2D g2,
 824:                          XYItemRendererState state,
 825:                          Rectangle2D dataArea,
 826:                          PlotRenderingInfo info,
 827:                          XYPlot plot,
 828:                          ValueAxis domainAxis,
 829:                          ValueAxis rangeAxis,
 830:                          XYDataset dataset,
 831:                          int series,
 832:                          int item,
 833:                          CrosshairState crosshairState,
 834:                          int pass) {
 835: 
 836:         // do nothing if item is not visible
 837:         if (!getItemVisible(series, item)) {
 838:             return;   
 839:         }
 840: 
 841:         // first pass draws the background (lines, for instance)
 842:         if (isLinePass(pass)) {
 843:             if (item == 0) {
 844:                 if (this.drawSeriesLineAsPath) {
 845:                     State s = (State) state;
 846:                     s.seriesPath.reset();
 847:                     s.lastPointGood = false;     
 848:                 }
 849:             }
 850: 
 851:             if (getItemLineVisible(series, item)) {
 852:                 if (this.drawSeriesLineAsPath) {
 853:                     drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 
 854:                             series, item, domainAxis, rangeAxis, dataArea);
 855:                 }
 856:                 else {
 857:                     drawPrimaryLine(state, g2, plot, dataset, pass, series, 
 858:                             item, domainAxis, rangeAxis, dataArea);
 859:                 }
 860:             }
 861:         }
 862:         // second pass adds shapes where the items are ..
 863:         else if (isItemPass(pass)) {
 864: 
 865:             // setup for collecting optional entity info...
 866:             EntityCollection entities = null;
 867:             if (info != null) {
 868:                 entities = info.getOwner().getEntityCollection();
 869:             }
 870: 
 871:             drawSecondaryPass(g2, plot, dataset, pass, series, item, 
 872:                     domainAxis, dataArea, rangeAxis, crosshairState, entities);
 873:         }
 874:     }
 875: 
 876:     /**
 877:      * Returns <code>true</code> if the specified pass is the one for drawing 
 878:      * lines.
 879:      * 
 880:      * @param pass  the pass.
 881:      * 
 882:      * @return A boolean.
 883:      */
 884:     protected boolean isLinePass(int pass) {
 885:         return pass == 0;
 886:     }
 887: 
 888:     /**
 889:      * Returns <code>true</code> if the specified pass is the one for drawing 
 890:      * items.
 891:      * 
 892:      * @param pass  the pass.
 893:      * 
 894:      * @return A boolean.
 895:      */
 896:     protected boolean isItemPass(int pass) {
 897:         return pass == 1;
 898:     }
 899: 
 900:     /**
 901:      * Draws the item (first pass). This method draws the lines
 902:      * connecting the items.
 903:      *
 904:      * @param g2  the graphics device.
 905:      * @param state  the renderer state.
 906:      * @param dataArea  the area within which the data is being drawn.
 907:      * @param plot  the plot (can be used to obtain standard color 
 908:      *              information etc).
 909:      * @param domainAxis  the domain axis.
 910:      * @param rangeAxis  the range axis.
 911:      * @param dataset  the dataset.
 912:      * @param pass  the pass.
 913:      * @param series  the series index (zero-based).
 914:      * @param item  the item index (zero-based).
 915:      */
 916:     protected void drawPrimaryLine(XYItemRendererState state,
 917:                                    Graphics2D g2,
 918:                                    XYPlot plot,
 919:                                    XYDataset dataset,
 920:                                    int pass,
 921:                                    int series,
 922:                                    int item,
 923:                                    ValueAxis domainAxis,
 924:                                    ValueAxis rangeAxis,
 925:                                    Rectangle2D dataArea) {
 926:         if (item == 0) {
 927:             return;
 928:         }
 929: 
 930:         // get the data point...
 931:         double x1 = dataset.getXValue(series, item);
 932:         double y1 = dataset.getYValue(series, item);
 933:         if (Double.isNaN(y1) || Double.isNaN(x1)) {
 934:             return;
 935:         }
 936: 
 937:         double x0 = dataset.getXValue(series, item - 1);
 938:         double y0 = dataset.getYValue(series, item - 1);
 939:         if (Double.isNaN(y0) || Double.isNaN(x0)) {
 940:             return;
 941:         }
 942: 
 943:         RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
 944:         RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
 945: 
 946:         double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
 947:         double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);
 948: 
 949:         double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
 950:         double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
 951: 
 952:         // only draw if we have good values
 953:         if (Double.isNaN(transX0) || Double.isNaN(transY0)
 954:             || Double.isNaN(transX1) || Double.isNaN(transY1)) {
 955:             return;
 956:         }
 957: 
 958:         PlotOrientation orientation = plot.getOrientation();
 959:         if (orientation == PlotOrientation.HORIZONTAL) {
 960:             state.workingLine.setLine(transY0, transX0, transY1, transX1);
 961:         }
 962:         else if (orientation == PlotOrientation.VERTICAL) {
 963:             state.workingLine.setLine(transX0, transY0, transX1, transY1);
 964:         }
 965: 
 966:         if (state.workingLine.intersects(dataArea)) {
 967:             drawFirstPassShape(g2, pass, series, item, state.workingLine);
 968:         }
 969:     }
 970: 
 971:     /**
 972:      * Draws the first pass shape.
 973:      * 
 974:      * @param g2  the graphics device.
 975:      * @param pass  the pass.
 976:      * @param series  the series index.
 977:      * @param item  the item index.
 978:      * @param shape  the shape.
 979:      */
 980:     protected void drawFirstPassShape(Graphics2D g2, int pass, int series,
 981:                                       int item, Shape shape) {
 982:         g2.setStroke(getItemStroke(series, item));
 983:         g2.setPaint(getItemPaint(series, item));
 984:         g2.draw(shape);
 985:     }
 986: 
 987: 
 988:     /**
 989:      * Draws the item (first pass). This method draws the lines
 990:      * connecting the items. Instead of drawing separate lines,
 991:      * a GeneralPath is constructed and drawn at the end of
 992:      * the series painting.
 993:      *
 994:      * @param g2  the graphics device.
 995:      * @param state  the renderer state.
 996:      * @param plot  the plot (can be used to obtain standard color information 
 997:      *              etc).
 998:      * @param dataset  the dataset.
 999:      * @param pass  the pass.
1000:      * @param series  the series index (zero-based).
1001:      * @param item  the item index (zero-based).
1002:      * @param domainAxis  the domain axis.
1003:      * @param rangeAxis  the range axis.
1004:      * @param dataArea  the area within which the data is being drawn.
1005:      */
1006:     protected void drawPrimaryLineAsPath(XYItemRendererState state,
1007:                                          Graphics2D g2, XYPlot plot,
1008:                                          XYDataset dataset,
1009:                                          int pass,
1010:                                          int series,
1011:                                          int item,
1012:                                          ValueAxis domainAxis,
1013:                                          ValueAxis rangeAxis,
1014:                                          Rectangle2D dataArea) {
1015: 
1016: 
1017:         RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
1018:         RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
1019: 
1020:         // get the data point...
1021:         double x1 = dataset.getXValue(series, item);
1022:         double y1 = dataset.getYValue(series, item);
1023:         double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
1024:         double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
1025: 
1026:         State s = (State) state;
1027:         // update path to reflect latest point
1028:         if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
1029:             float x = (float) transX1;
1030:             float y = (float) transY1;
1031:             PlotOrientation orientation = plot.getOrientation();
1032:             if (orientation == PlotOrientation.HORIZONTAL) {
1033:                 x = (float) transY1;
1034:                 y = (float) transX1;
1035:             }
1036:             if (s.isLastPointGood()) {
1037:                 s.seriesPath.lineTo(x, y);
1038:             }
1039:             else {
1040:                 s.seriesPath.moveTo(x, y);
1041:             }
1042:             s.setLastPointGood(true);
1043:         }
1044:         else {
1045:             s.setLastPointGood(false);
1046:         }
1047:         // if this is the last item, draw the path ...
1048:         if (item == dataset.getItemCount(series) - 1) {
1049:             // draw path
1050:             drawFirstPassShape(g2, pass, series, item, s.seriesPath);
1051:         }
1052:     }
1053: 
1054:     /**
1055:      * Draws the item shapes and adds chart entities (second pass). This method 
1056:      * draws the shapes which mark the item positions. If <code>entities</code> 
1057:      * is not <code>null</code> it will be populated with entity information.
1058:      *
1059:      * @param g2  the graphics device.
1060:      * @param dataArea  the area within which the data is being drawn.
1061:      * @param plot  the plot (can be used to obtain standard color 
1062:      *              information etc).
1063:      * @param domainAxis  the domain axis.
1064:      * @param rangeAxis  the range axis.
1065:      * @param dataset  the dataset.
1066:      * @param pass  the pass.
1067:      * @param series  the series index (zero-based).
1068:      * @param item  the item index (zero-based).
1069:      * @param crosshairState  the crosshair state.
1070:      * @param entities the entity collection.
1071:      */
1072:     protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 
1073:                                      XYDataset dataset,
1074:                                      int pass, int series, int item,
1075:                                      ValueAxis domainAxis, 
1076:                                      Rectangle2D dataArea,
1077:                                      ValueAxis rangeAxis, 
1078:                                      CrosshairState crosshairState,
1079:                                      EntityCollection entities) {
1080: 
1081:         Shape entityArea = null;
1082: 
1083:         // get the data point...
1084:         double x1 = dataset.getXValue(series, item);
1085:         double y1 = dataset.getYValue(series, item);
1086:         if (Double.isNaN(y1) || Double.isNaN(x1)) {
1087:             return;
1088:         }
1089: 
1090:         PlotOrientation orientation = plot.getOrientation();
1091:         RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
1092:         RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
1093:         double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
1094:         double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
1095: 
1096:         if (getItemShapeVisible(series, item)) {
1097:             Shape shape = getItemShape(series, item);
1098:             if (orientation == PlotOrientation.HORIZONTAL) {
1099:                 shape = ShapeUtilities.createTranslatedShape(shape, transY1, 
1100:                         transX1);
1101:             }
1102:             else if (orientation == PlotOrientation.VERTICAL) {
1103:                 shape = ShapeUtilities.createTranslatedShape(shape, transX1, 
1104:                         transY1);
1105:             }
1106:             entityArea = shape;
1107:             if (shape.intersects(dataArea)) {
1108:                 if (getItemShapeFilled(series, item)) {
1109:                     if (this.useFillPaint) {
1110:                         g2.setPaint(getItemFillPaint(series, item));
1111:                     }
1112:                     else {
1113:                         g2.setPaint(getItemPaint(series, item));
1114:                     }
1115:                     g2.fill(shape);
1116:                 }
1117:                 if (this.drawOutlines) {
1118:                     if (getUseOutlinePaint()) {
1119:                         g2.setPaint(getItemOutlinePaint(series, item));
1120:                     }
1121:                     else {
1122:                         g2.setPaint(getItemPaint(series, item));
1123:                     }
1124:                     g2.setStroke(getItemOutlineStroke(series, item));
1125:                     g2.draw(shape);
1126:                 }
1127:             }
1128:         }
1129: 
1130:         // draw the item label if there is one...
1131:         if (isItemLabelVisible(series, item)) {
1132:             double xx = transX1;
1133:             double yy = transY1;
1134:             if (orientation == PlotOrientation.HORIZONTAL) {
1135:                 xx = transY1;
1136:                 yy = transX1;
1137:             }          
1138:             drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 
1139:                     (y1 < 0.0));
1140:         }
1141: 
1142:         int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
1143:         int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
1144:         updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 
1145:                 rangeAxisIndex, transX1, transY1, plot.getOrientation());
1146: 
1147:         // add an entity for the item...
1148:         if (entities != null) {
1149:             addEntity(entities, entityArea, dataset, series, item, transX1, 
1150:                     transY1);
1151:         }
1152:     }
1153: 
1154: 
1155:     /**
1156:      * Returns a legend item for the specified series.
1157:      *
1158:      * @param datasetIndex  the dataset index (zero-based).
1159:      * @param series  the series index (zero-based).
1160:      *
1161:      * @return A legend item for the series.
1162:      */
1163:     public LegendItem getLegendItem(int datasetIndex, int series) {
1164: 
1165:         XYPlot plot = getPlot();
1166:         if (plot == null) {
1167:             return null;
1168:         }
1169: 
1170:         LegendItem result = null;
1171:         XYDataset dataset = plot.getDataset(datasetIndex);
1172:         if (dataset != null) {
1173:             if (getItemVisible(series, 0)) {
1174:                 String label = getLegendItemLabelGenerator().generateLabel(
1175:                         dataset, series);
1176:                 String description = label;
1177:                 String toolTipText = null;
1178:                 if (getLegendItemToolTipGenerator() != null) {
1179:                     toolTipText = getLegendItemToolTipGenerator().generateLabel(
1180:                             dataset, series);
1181:                 }
1182:                 String urlText = null;
1183:                 if (getLegendItemURLGenerator() != null) {
1184:                     urlText = getLegendItemURLGenerator().generateLabel(
1185:                             dataset, series);
1186:                 }
1187:                 boolean shapeIsVisible = getItemShapeVisible(series, 0);
1188:                 Shape shape = getSeriesShape(series);
1189:                 boolean shapeIsFilled = getItemShapeFilled(series, 0);
1190:                 Paint fillPaint = (this.useFillPaint 
1191:                     ? getSeriesFillPaint(series) : getSeriesPaint(series));
1192:                 boolean shapeOutlineVisible = this.drawOutlines;  
1193:                 Paint outlinePaint = (this.useOutlinePaint 
1194:                     ? getSeriesOutlinePaint(series) 
1195:                     : getSeriesPaint(series));
1196:                 Stroke outlineStroke = getSeriesOutlineStroke(series);
1197:                 boolean lineVisible = getItemLineVisible(series, 0);
1198:                 Stroke lineStroke = getSeriesStroke(series);
1199:                 Paint linePaint = getSeriesPaint(series);
1200:                 result = new LegendItem(label, description, toolTipText, 
1201:                         urlText, shapeIsVisible, shape, shapeIsFilled, 
1202:                         fillPaint, shapeOutlineVisible, outlinePaint, 
1203:                         outlineStroke, lineVisible, this.legendLine, 
1204:                         lineStroke, linePaint);
1205:                 result.setSeriesIndex(series);
1206:                 result.setDatasetIndex(datasetIndex);
1207:             }
1208:         }
1209: 
1210:         return result;
1211: 
1212:     }
1213:     
1214:     /**
1215:      * Returns a clone of the renderer.
1216:      * 
1217:      * @return A clone.
1218:      * 
1219:      * @throws CloneNotSupportedException if the clone cannot be created.
1220:      */
1221:     public Object clone() throws CloneNotSupportedException {
1222:         XYLineAndShapeRenderer clone = (XYLineAndShapeRenderer) super.clone();
1223:         clone.seriesLinesVisible 
1224:                 = (BooleanList) this.seriesLinesVisible.clone();
1225:         if (this.legendLine != null) {
1226:             clone.legendLine = ShapeUtilities.clone(this.legendLine);
1227:         }
1228:         clone.seriesShapesVisible 
1229:                 = (BooleanList) this.seriesShapesVisible.clone();
1230:         clone.seriesShapesFilled 
1231:                 = (BooleanList) this.seriesShapesFilled.clone();
1232:         return clone;
1233:     }
1234:     
1235:     /**
1236:      * Tests this renderer for equality with an arbitrary object.
1237:      *
1238:      * @param obj  the object (<code>null</code> permitted).
1239:      *
1240:      * @return <code>true</code> or <code>false</code>.
1241:      */
1242:     public boolean equals(Object obj) {
1243: 
1244:         if (obj == this) {
1245:             return true;
1246:         }
1247:         if (!(obj instanceof XYLineAndShapeRenderer)) {
1248:             return false;
1249:         }
1250:         if (!super.equals(obj)) {
1251:             return false;
1252:         }
1253:         XYLineAndShapeRenderer that = (XYLineAndShapeRenderer) obj;
1254:         if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
1255:             return false;
1256:         }
1257:         if (!ObjectUtilities.equal(
1258:             this.seriesLinesVisible, that.seriesLinesVisible)
1259:         ) {
1260:             return false;
1261:         }
1262:         if (this.baseLinesVisible != that.baseLinesVisible) {
1263:             return false;
1264:         }
1265:         if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
1266:             return false;   
1267:         }
1268:         if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
1269:             return false;
1270:         }
1271:         if (!ObjectUtilities.equal(
1272:             this.seriesShapesVisible, that.seriesShapesVisible)
1273:         ) {
1274:             return false;
1275:         }
1276:         if (this.baseShapesVisible != that.baseShapesVisible) {
1277:             return false;
1278:         }
1279:         if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
1280:             return false;
1281:         }
1282:         if (!ObjectUtilities.equal(
1283:             this.seriesShapesFilled, that.seriesShapesFilled)
1284:         ) {
1285:             return false;
1286:         }
1287:         if (this.baseShapesFilled != that.baseShapesFilled) {
1288:             return false;
1289:         }
1290:         if (this.drawOutlines != that.drawOutlines) {
1291:             return false;
1292:         }
1293:         if (this.useOutlinePaint != that.useOutlinePaint) {
1294:             return false;
1295:         }
1296:         if (this.useFillPaint != that.useFillPaint) {
1297:             return false;
1298:         }
1299:         if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
1300:             return false;
1301:         }
1302:         return true;
1303: 
1304:     }
1305:     
1306:     /**
1307:      * Provides serialization support.
1308:      *
1309:      * @param stream  the input stream.
1310:      *
1311:      * @throws IOException  if there is an I/O error.
1312:      * @throws ClassNotFoundException  if there is a classpath problem.
1313:      */
1314:     private void readObject(ObjectInputStream stream) 
1315:             throws IOException, ClassNotFoundException {
1316:         stream.defaultReadObject();
1317:         this.legendLine = SerialUtilities.readShape(stream);
1318:     }
1319:     
1320:     /**
1321:      * Provides serialization support.
1322:      *
1323:      * @param stream  the output stream.
1324:      *
1325:      * @throws IOException  if there is an I/O error.
1326:      */
1327:     private void writeObject(ObjectOutputStream stream) throws IOException {
1328:         stream.defaultWriteObject();
1329:         SerialUtilities.writeShape(this.legendLine, stream);
1330:     }
1331:   
1332: }