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