Source for org.jfree.chart.renderer.category.LineAndShapeRenderer

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * -------------------------
  28:  * LineAndShapeRenderer.java
  29:  * -------------------------
  30:  * (C) Copyright 2001-2006, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Mark Watson (www.markwatson.com);
  34:  *                   Jeremy Bowman;
  35:  *                   Richard Atkinson;
  36:  *                   Christian W. Zuckschwerdt;
  37:  *
  38:  * $Id: LineAndShapeRenderer.java,v 1.18.2.7 2006/10/06 15:02:16 mungady Exp $
  39:  *
  40:  * Changes
  41:  * -------
  42:  * 23-Oct-2001 : Version 1 (DG);
  43:  * 15-Nov-2001 : Modified to allow for null data values (DG);
  44:  * 16-Jan-2002 : Renamed HorizontalCategoryItemRenderer.java 
  45:  *               --> CategoryItemRenderer.java (DG);
  46:  * 05-Feb-2002 : Changed return type of the drawCategoryItem method from void 
  47:  *               to Shape, as part of the tooltips implementation (DG);
  48:  * 11-May-2002 : Support for value label drawing (JB);
  49:  * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG);
  50:  * 25-Jun-2002 : Removed redundant import (DG);
  51:  * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 
  52:  *               for HTML image maps (RA);
  53:  * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  54:  * 11-Oct-2002 : Added new constructor to incorporate tool tip and URL 
  55:  *               generators (DG);
  56:  * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
  57:  *               CategoryToolTipGenerator interface (DG);
  58:  * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
  59:  * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis 
  60:  *               for category spacing (DG);
  61:  * 17-Jan-2003 : Moved plot classes to a separate package (DG);
  62:  * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in drawItem()
  63:  *               method (DG);
  64:  * 12-May-2003 : Modified to take into account the plot orientation (DG);
  65:  * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
  66:  * 30-Jul-2003 : Modified entity constructor (CZ);
  67:  * 22-Sep-2003 : Fixed cloning (DG);
  68:  * 10-Feb-2004 : Small change to drawItem() method to make cut-and-paste 
  69:  *               override easier (DG);
  70:  * 16-Jun-2004 : Fixed bug (id=972454) with label positioning on horizontal 
  71:  *               charts (DG);
  72:  * 15-Oct-2004 : Updated equals() method (DG);
  73:  * 05-Nov-2004 : Modified drawItem() signature (DG);
  74:  * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
  75:  * 27-Jan-2005 : Changed attribute names, modified constructor and removed 
  76:  *               constants (DG);
  77:  * 01-Feb-2005 : Removed unnecessary constants (DG);
  78:  * 15-Mar-2005 : Fixed bug 1163897, concerning outlines for shapes (DG);
  79:  * 13-Apr-2005 : Check flags that control series visibility (DG);
  80:  * 20-Apr-2005 : Use generators for legend labels, tooltips and URLs (DG);
  81:  * 09-Jun-2005 : Use addItemEntity() method (DG);
  82:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  83:  * 25-May-2006 : Added check to drawItem() to detect when both the line and
  84:  *               the shape are not visible (DG);
  85:  * 
  86:  */
  87: 
  88: package org.jfree.chart.renderer.category;
  89: 
  90: import java.awt.Graphics2D;
  91: import java.awt.Paint;
  92: import java.awt.Shape;
  93: import java.awt.Stroke;
  94: import java.awt.geom.Line2D;
  95: import java.awt.geom.Rectangle2D;
  96: import java.io.Serializable;
  97: 
  98: import org.jfree.chart.LegendItem;
  99: import org.jfree.chart.axis.CategoryAxis;
 100: import org.jfree.chart.axis.ValueAxis;
 101: import org.jfree.chart.entity.EntityCollection;
 102: import org.jfree.chart.event.RendererChangeEvent;
 103: import org.jfree.chart.plot.CategoryPlot;
 104: import org.jfree.chart.plot.PlotOrientation;
 105: import org.jfree.data.category.CategoryDataset;
 106: import org.jfree.util.BooleanList;
 107: import org.jfree.util.BooleanUtilities;
 108: import org.jfree.util.ObjectUtilities;
 109: import org.jfree.util.PublicCloneable;
 110: import org.jfree.util.ShapeUtilities;
 111: 
 112: /**
 113:  * A renderer that draws shapes for each data item, and lines between data 
 114:  * items (for use with the {@link CategoryPlot} class).
 115:  */
 116: public class LineAndShapeRenderer extends AbstractCategoryItemRenderer 
 117:                                   implements Cloneable, PublicCloneable, 
 118:                                              Serializable {
 119: 
 120:     /** For serialization. */
 121:     private static final long serialVersionUID = -197749519869226398L;
 122:     
 123:     /** A flag that controls whether or not lines are visible for ALL series. */
 124:     private Boolean linesVisible;
 125: 
 126:     /** 
 127:      * A table of flags that control (per series) whether or not lines are 
 128:      * visible. 
 129:      */
 130:     private BooleanList seriesLinesVisible;
 131: 
 132:     /** 
 133:      * A flag indicating whether or not lines are drawn between non-null 
 134:      * points. 
 135:      */
 136:     private boolean baseLinesVisible;
 137: 
 138:     /** 
 139:      * A flag that controls whether or not shapes are visible for ALL series. 
 140:      */
 141:     private Boolean shapesVisible;
 142: 
 143:     /** 
 144:      * A table of flags that control (per series) whether or not shapes are 
 145:      * visible. 
 146:      */
 147:     private BooleanList seriesShapesVisible;
 148: 
 149:     /** The default value returned by the getShapeVisible() method. */
 150:     private boolean baseShapesVisible;
 151: 
 152:     /** A flag that controls whether or not shapes are filled for ALL series. */
 153:     private Boolean shapesFilled;
 154:     
 155:     /** 
 156:      * A table of flags that control (per series) whether or not shapes are 
 157:      * filled. 
 158:      */
 159:     private BooleanList seriesShapesFilled;
 160:     
 161:     /** The default value returned by the getShapeFilled() method. */
 162:     private boolean baseShapesFilled;
 163:     
 164:     /** 
 165:      * A flag that controls whether the fill paint is used for filling 
 166:      * shapes. 
 167:      */
 168:     private boolean useFillPaint;
 169: 
 170:     /** A flag that controls whether outlines are drawn for shapes. */
 171:     private boolean drawOutlines;
 172:         
 173:     /** 
 174:      * A flag that controls whether the outline paint is used for drawing shape 
 175:      * outlines - if not, the regular series paint is used. 
 176:      */
 177:     private boolean useOutlinePaint;
 178: 
 179:     /**
 180:      * Creates a renderer with both lines and shapes visible by default.
 181:      */
 182:     public LineAndShapeRenderer() {
 183:         this(true, true);
 184:     }
 185: 
 186:     /**
 187:      * Creates a new renderer with lines and/or shapes visible.
 188:      * 
 189:      * @param lines  draw lines?
 190:      * @param shapes  draw shapes?
 191:      */
 192:     public LineAndShapeRenderer(boolean lines, boolean shapes) {
 193:         super();
 194:         this.linesVisible = null;
 195:         this.seriesLinesVisible = new BooleanList();
 196:         this.baseLinesVisible = lines;
 197:         this.shapesVisible = null;
 198:         this.seriesShapesVisible = new BooleanList();
 199:         this.baseShapesVisible = shapes;
 200:         this.shapesFilled = null;
 201:         this.seriesShapesFilled = new BooleanList();
 202:         this.baseShapesFilled = true;
 203:         this.useFillPaint = false;
 204:         this.drawOutlines = true;
 205:         this.useOutlinePaint = false;
 206:     }
 207:     
 208:     // LINES VISIBLE
 209: 
 210:     /**
 211:      * Returns the flag used to control whether or not the line for an item is 
 212:      * visible.
 213:      *
 214:      * @param series  the series index (zero-based).
 215:      * @param item  the item index (zero-based).
 216:      *
 217:      * @return A boolean.
 218:      */
 219:     public boolean getItemLineVisible(int series, int item) {
 220:         Boolean flag = this.linesVisible;
 221:         if (flag == null) {
 222:             flag = getSeriesLinesVisible(series);
 223:         }
 224:         if (flag != null) {
 225:             return flag.booleanValue();
 226:         }
 227:         else {
 228:             return this.baseLinesVisible;   
 229:         }
 230:     }
 231: 
 232:     /**
 233:      * Returns a flag that controls whether or not lines are drawn for ALL 
 234:      * series.  If this flag is <code>null</code>, then the "per series" 
 235:      * settings will apply.
 236:      * 
 237:      * @return A flag (possibly <code>null</code>).
 238:      */
 239:     public Boolean getLinesVisible() {
 240:         return this.linesVisible;   
 241:     }
 242:     
 243:     /**
 244:      * Sets a flag that controls whether or not lines are drawn between the 
 245:      * items in ALL series, and sends a {@link RendererChangeEvent} to all 
 246:      * registered listeners.  You need to set this to <code>null</code> if you 
 247:      * want the "per series" settings to apply.
 248:      *
 249:      * @param visible  the flag (<code>null</code> permitted).
 250:      */
 251:     public void setLinesVisible(Boolean visible) {
 252:         this.linesVisible = visible;
 253:         notifyListeners(new RendererChangeEvent(this));
 254:     }
 255: 
 256:     /**
 257:      * Sets a flag that controls whether or not lines are drawn between the 
 258:      * items in ALL series, and sends a {@link RendererChangeEvent} to all 
 259:      * registered listeners.
 260:      *
 261:      * @param visible  the flag.
 262:      */
 263:     public void setLinesVisible(boolean visible) {
 264:         setLinesVisible(BooleanUtilities.valueOf(visible));
 265:     }
 266: 
 267:     /**
 268:      * Returns the flag used to control whether or not the lines for a series 
 269:      * are visible.
 270:      *
 271:      * @param series  the series index (zero-based).
 272:      *
 273:      * @return The flag (possibly <code>null</code>).
 274:      */
 275:     public Boolean getSeriesLinesVisible(int series) {
 276:         return this.seriesLinesVisible.getBoolean(series);
 277:     }
 278: 
 279:     /**
 280:      * Sets the 'lines visible' flag for a series.
 281:      *
 282:      * @param series  the series index (zero-based).
 283:      * @param flag  the flag (<code>null</code> permitted).
 284:      */
 285:     public void setSeriesLinesVisible(int series, Boolean flag) {
 286:         this.seriesLinesVisible.setBoolean(series, flag);
 287:         notifyListeners(new RendererChangeEvent(this));
 288:     }
 289: 
 290:     /**
 291:      * Sets the 'lines visible' flag for a series.
 292:      * 
 293:      * @param series  the series index (zero-based).
 294:      * @param visible  the flag.
 295:      */
 296:     public void setSeriesLinesVisible(int series, boolean visible) {
 297:         setSeriesLinesVisible(series, BooleanUtilities.valueOf(visible));
 298:     }
 299:     
 300:     /**
 301:      * Returns the base 'lines visible' attribute.
 302:      *
 303:      * @return The base flag.
 304:      */
 305:     public boolean getBaseLinesVisible() {
 306:         return this.baseLinesVisible;
 307:     }
 308: 
 309:     /**
 310:      * Sets the base 'lines visible' flag.
 311:      *
 312:      * @param flag  the flag.
 313:      */
 314:     public void setBaseLinesVisible(boolean flag) {
 315:         this.baseLinesVisible = flag;
 316:         notifyListeners(new RendererChangeEvent(this));
 317:     }
 318: 
 319:     // SHAPES VISIBLE
 320: 
 321:     /**
 322:      * Returns the flag used to control whether or not the shape for an item is 
 323:      * visible.
 324:      *
 325:      * @param series  the series index (zero-based).
 326:      * @param item  the item index (zero-based).
 327:      *
 328:      * @return A boolean.
 329:      */
 330:     public boolean getItemShapeVisible(int series, int item) {
 331:         Boolean flag = this.shapesVisible;
 332:         if (flag == null) {
 333:             flag = getSeriesShapesVisible(series);
 334:         }
 335:         if (flag != null) {
 336:             return flag.booleanValue();
 337:         }
 338:         else {
 339:             return this.baseShapesVisible;   
 340:         }
 341:     }
 342: 
 343:     /**
 344:      * Returns the flag that controls whether the shapes are visible for the 
 345:      * items in ALL series.
 346:      * 
 347:      * @return The flag (possibly <code>null</code>).
 348:      */
 349:     public Boolean getShapesVisible() {
 350:         return this.shapesVisible;    
 351:     }
 352:     
 353:     /**
 354:      * Sets the 'shapes visible' for ALL series and sends a 
 355:      * {@link RendererChangeEvent} to all registered listeners.
 356:      *
 357:      * @param visible  the flag (<code>null</code> permitted).
 358:      */
 359:     public void setShapesVisible(Boolean visible) {
 360:         this.shapesVisible = visible;
 361:         notifyListeners(new RendererChangeEvent(this));
 362:     }
 363: 
 364:     /**
 365:      * Sets the 'shapes visible' for ALL series and sends a 
 366:      * {@link RendererChangeEvent} to all registered listeners.
 367:      * 
 368:      * @param visible  the flag.
 369:      */
 370:     public void setShapesVisible(boolean visible) {
 371:         setShapesVisible(BooleanUtilities.valueOf(visible));
 372:     }
 373: 
 374:     /**
 375:      * Returns the flag used to control whether or not the shapes for a series
 376:      * are visible.
 377:      *
 378:      * @param series  the series index (zero-based).
 379:      *
 380:      * @return A boolean.
 381:      */
 382:     public Boolean getSeriesShapesVisible(int series) {
 383:         return this.seriesShapesVisible.getBoolean(series);
 384:     }
 385: 
 386:     /**
 387:      * Sets the 'shapes visible' flag for a series and sends a 
 388:      * {@link RendererChangeEvent} to all registered listeners.
 389:      * 
 390:      * @param series  the series index (zero-based).
 391:      * @param visible  the flag.
 392:      */
 393:     public void setSeriesShapesVisible(int series, boolean visible) {
 394:         setSeriesShapesVisible(series, BooleanUtilities.valueOf(visible));
 395:     }
 396:     
 397:     /**
 398:      * Sets the 'shapes visible' flag for a series and sends a 
 399:      * {@link RendererChangeEvent} to all registered listeners.
 400:      *
 401:      * @param series  the series index (zero-based).
 402:      * @param flag  the flag.
 403:      */
 404:     public void setSeriesShapesVisible(int series, Boolean flag) {
 405:         this.seriesShapesVisible.setBoolean(series, flag);
 406:         notifyListeners(new RendererChangeEvent(this));
 407:     }
 408: 
 409:     /**
 410:      * Returns the base 'shape visible' attribute.
 411:      *
 412:      * @return The base flag.
 413:      */
 414:     public boolean getBaseShapesVisible() {
 415:         return this.baseShapesVisible;
 416:     }
 417: 
 418:     /**
 419:      * Sets the base 'shapes visible' flag.
 420:      *
 421:      * @param flag  the flag.
 422:      */
 423:     public void setBaseShapesVisible(boolean flag) {
 424:         this.baseShapesVisible = flag;
 425:         notifyListeners(new RendererChangeEvent(this));
 426:     }
 427: 
 428:     /**
 429:      * Returns <code>true</code> if outlines should be drawn for shapes, and 
 430:      * <code>false</code> otherwise.
 431:      * 
 432:      * @return A boolean.
 433:      */
 434:     public boolean getDrawOutlines() {
 435:         return this.drawOutlines;
 436:     }
 437:     
 438:     /**
 439:      * Sets the flag that controls whether outlines are drawn for 
 440:      * shapes, and sends a {@link RendererChangeEvent} to all registered 
 441:      * listeners. 
 442:      * <P>
 443:      * In some cases, shapes look better if they do NOT have an outline, but 
 444:      * this flag allows you to set your own preference.
 445:      * 
 446:      * @param flag  the flag.
 447:      */
 448:     public void setDrawOutlines(boolean flag) {
 449:         this.drawOutlines = flag;
 450:         notifyListeners(new RendererChangeEvent(this));
 451:     }
 452:     
 453:     /**
 454:      * Returns the flag that controls whether the outline paint is used for 
 455:      * shape outlines.  If not, the regular series paint is used.
 456:      * 
 457:      * @return A boolean.
 458:      */
 459:     public boolean getUseOutlinePaint() {
 460:         return this.useOutlinePaint;   
 461:     }
 462:     
 463:     /**
 464:      * Sets the flag that controls whether the outline paint is used for shape 
 465:      * outlines.
 466:      * 
 467:      * @param use  the flag.
 468:      */
 469:     public void setUseOutlinePaint(boolean use) {
 470:         this.useOutlinePaint = use;   
 471:     }
 472: 
 473:     // SHAPES FILLED
 474:     
 475:     /**
 476:      * Returns the flag used to control whether or not the shape for an item 
 477:      * is filled. The default implementation passes control to the 
 478:      * <code>getSeriesShapesFilled</code> method. You can override this method
 479:      * if you require different behaviour.
 480:      *
 481:      * @param series  the series index (zero-based).
 482:      * @param item  the item index (zero-based).
 483:      *
 484:      * @return A boolean.
 485:      */
 486:     public boolean getItemShapeFilled(int series, int item) {
 487:         return getSeriesShapesFilled(series);
 488:     }
 489: 
 490:     /**
 491:      * Returns the flag used to control whether or not the shapes for a series 
 492:      * are filled. 
 493:      *
 494:      * @param series  the series index (zero-based).
 495:      *
 496:      * @return A boolean.
 497:      */
 498:     public boolean getSeriesShapesFilled(int series) {
 499: 
 500:         // return the overall setting, if there is one...
 501:         if (this.shapesFilled != null) {
 502:             return this.shapesFilled.booleanValue();
 503:         }
 504: 
 505:         // otherwise look up the paint table
 506:         Boolean flag = this.seriesShapesFilled.getBoolean(series);
 507:         if (flag != null) {
 508:             return flag.booleanValue();
 509:         }
 510:         else {
 511:             return this.baseShapesFilled;
 512:         } 
 513: 
 514:     }
 515:     
 516:     /**
 517:      * Returns the flag that controls whether or not shapes are filled for 
 518:      * ALL series.
 519:      * 
 520:      * @return A Boolean.
 521:      */
 522:     public Boolean getShapesFilled() {
 523:         return this.shapesFilled;
 524:     }
 525: 
 526:     /**
 527:      * Sets the 'shapes filled' for ALL series.
 528:      * 
 529:      * @param filled  the flag.
 530:      */
 531:     public void setShapesFilled(boolean filled) {
 532:         if (filled) {
 533:             setShapesFilled(Boolean.TRUE);
 534:         }
 535:         else {
 536:             setShapesFilled(Boolean.FALSE);
 537:         }
 538:     }
 539:     
 540:     /**
 541:      * Sets the 'shapes filled' for ALL series.
 542:      * 
 543:      * @param filled  the flag (<code>null</code> permitted).
 544:      */
 545:     public void setShapesFilled(Boolean filled) {
 546:         this.shapesFilled = filled;
 547:     }
 548:     
 549:     /**
 550:      * Sets the 'shapes filled' flag for a series.
 551:      *
 552:      * @param series  the series index (zero-based).
 553:      * @param filled  the flag.
 554:      */
 555:     public void setSeriesShapesFilled(int series, Boolean filled) {
 556:         this.seriesShapesFilled.setBoolean(series, filled);
 557:     }
 558: 
 559:     /**
 560:      * Sets the 'shapes filled' flag for a series.
 561:      *
 562:      * @param series  the series index (zero-based).
 563:      * @param filled  the flag.
 564:      */
 565:     public void setSeriesShapesFilled(int series, boolean filled) {
 566:         this.seriesShapesFilled.setBoolean(
 567:             series, BooleanUtilities.valueOf(filled)
 568:         );
 569:     }
 570: 
 571:     /**
 572:      * Returns the base 'shape filled' attribute.
 573:      *
 574:      * @return The base flag.
 575:      */
 576:     public boolean getBaseShapesFilled() {
 577:         return this.baseShapesFilled;
 578:     }
 579: 
 580:     /**
 581:      * Sets the base 'shapes filled' flag.
 582:      *
 583:      * @param flag  the flag.
 584:      */
 585:     public void setBaseShapesFilled(boolean flag) {
 586:         this.baseShapesFilled = flag;
 587:     }
 588: 
 589:     /**
 590:      * Returns <code>true</code> if the renderer should use the fill paint 
 591:      * setting to fill shapes, and <code>false</code> if it should just
 592:      * use the regular paint.
 593:      * 
 594:      * @return A boolean.
 595:      */
 596:     public boolean getUseFillPaint() {
 597:         return this.useFillPaint;
 598:     }
 599:     
 600:     /**
 601:      * Sets the flag that controls whether the fill paint is used to fill 
 602:      * shapes, and sends a {@link RendererChangeEvent} to all 
 603:      * registered listeners.
 604:      * 
 605:      * @param flag  the flag.
 606:      */
 607:     public void setUseFillPaint(boolean flag) {
 608:         this.useFillPaint = flag;
 609:         notifyListeners(new RendererChangeEvent(this));
 610:     }
 611:     
 612:     /**
 613:      * Returns a legend item for a series.
 614:      *
 615:      * @param datasetIndex  the dataset index (zero-based).
 616:      * @param series  the series index (zero-based).
 617:      *
 618:      * @return The legend item.
 619:      */
 620:     public LegendItem getLegendItem(int datasetIndex, int series) {
 621: 
 622:         CategoryPlot cp = getPlot();
 623:         if (cp == null) {
 624:             return null;
 625:         }
 626: 
 627:         if (isSeriesVisible(series) && isSeriesVisibleInLegend(series)) {
 628:             CategoryDataset dataset;
 629:             dataset = cp.getDataset(datasetIndex);
 630:             String label = getLegendItemLabelGenerator().generateLabel(
 631:                     dataset, series);
 632:             String description = label;
 633:             String toolTipText = null; 
 634:             if (getLegendItemToolTipGenerator() != null) {
 635:                 toolTipText = getLegendItemToolTipGenerator().generateLabel(
 636:                         dataset, series);   
 637:             }
 638:             String urlText = null;
 639:             if (getLegendItemURLGenerator() != null) {
 640:                 urlText = getLegendItemURLGenerator().generateLabel(
 641:                         dataset, series);   
 642:             }
 643:             Shape shape = getSeriesShape(series);
 644:             Paint paint = getSeriesPaint(series);
 645:             Paint fillPaint = (this.useFillPaint 
 646:                     ? getItemFillPaint(series, 0) : paint);
 647:             boolean shapeOutlineVisible = this.drawOutlines;
 648:             Paint outlinePaint = (this.useOutlinePaint 
 649:                     ? getItemOutlinePaint(series, 0) : paint);
 650:             Stroke outlineStroke = getSeriesOutlineStroke(series);
 651:             boolean lineVisible = getItemLineVisible(series, 0);
 652:             boolean shapeVisible = getItemShapeVisible(series, 0);
 653:             return new LegendItem(label, description, toolTipText, 
 654:                     urlText, shapeVisible, shape, getItemShapeFilled(series, 0),
 655:                     fillPaint, shapeOutlineVisible, outlinePaint, outlineStroke,
 656:                     lineVisible, new Line2D.Double(-7.0, 0.0, 7.0, 0.0),
 657:                     getItemStroke(series, 0), getItemPaint(series, 0));
 658:         }
 659:         return null;
 660: 
 661:     }
 662: 
 663:     /**
 664:      * This renderer uses two passes to draw the data.
 665:      * 
 666:      * @return The pass count (<code>2</code> for this renderer).
 667:      */
 668:     public int getPassCount() {
 669:         return 2;   
 670:     }
 671:     
 672:     /**
 673:      * Draw a single data item.
 674:      *
 675:      * @param g2  the graphics device.
 676:      * @param state  the renderer state.
 677:      * @param dataArea  the area in which the data is drawn.
 678:      * @param plot  the plot.
 679:      * @param domainAxis  the domain axis.
 680:      * @param rangeAxis  the range axis.
 681:      * @param dataset  the dataset.
 682:      * @param row  the row index (zero-based).
 683:      * @param column  the column index (zero-based).
 684:      * @param pass  the pass index.
 685:      */
 686:     public void drawItem(Graphics2D g2, CategoryItemRendererState state,
 687:             Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
 688:             ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
 689:             int pass) {
 690: 
 691:         // do nothing if item is not visible
 692:         if (!getItemVisible(row, column)) {
 693:             return;   
 694:         }
 695:         
 696:         // do nothing if both the line and shape are not visible
 697:         if (!getItemLineVisible(row, column) 
 698:                 && !getItemShapeVisible(row, column)) {
 699:             return;
 700:         }
 701: 
 702:         // nothing is drawn for null...
 703:         Number v = dataset.getValue(row, column);
 704:         if (v == null) {
 705:             return;
 706:         }
 707: 
 708:         PlotOrientation orientation = plot.getOrientation();
 709: 
 710:         // current data point...
 711:         double x1 = domainAxis.getCategoryMiddle(column, getColumnCount(), 
 712:                 dataArea, plot.getDomainAxisEdge());
 713:         double value = v.doubleValue();
 714:         double y1 = rangeAxis.valueToJava2D(value, dataArea, 
 715:                 plot.getRangeAxisEdge());
 716: 
 717:         if (pass == 0 && getItemLineVisible(row, column)) {
 718:             if (column != 0) {
 719:                 Number previousValue = dataset.getValue(row, column - 1);
 720:                 if (previousValue != null) {
 721:                     // previous data point...
 722:                     double previous = previousValue.doubleValue();
 723:                     double x0 = domainAxis.getCategoryMiddle(column - 1, 
 724:                             getColumnCount(), dataArea, 
 725:                             plot.getDomainAxisEdge());
 726:                     double y0 = rangeAxis.valueToJava2D(previous, dataArea, 
 727:                             plot.getRangeAxisEdge());
 728: 
 729:                     Line2D line = null;
 730:                     if (orientation == PlotOrientation.HORIZONTAL) {
 731:                         line = new Line2D.Double(y0, x0, y1, x1);
 732:                     }
 733:                     else if (orientation == PlotOrientation.VERTICAL) {
 734:                         line = new Line2D.Double(x0, y0, x1, y1);
 735:                     }
 736:                     g2.setPaint(getItemPaint(row, column));
 737:                     g2.setStroke(getItemStroke(row, column));
 738:                     g2.draw(line);
 739:                 }
 740:             }
 741:         }
 742: 
 743:         if (pass == 1) {
 744:             Shape shape = getItemShape(row, column);
 745:             if (orientation == PlotOrientation.HORIZONTAL) {
 746:                 shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
 747:             }
 748:             else if (orientation == PlotOrientation.VERTICAL) {
 749:                 shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
 750:             }
 751: 
 752:             if (getItemShapeVisible(row, column)) {
 753:                 if (getItemShapeFilled(row, column)) {
 754:                     if (this.useFillPaint) {
 755:                         g2.setPaint(getItemFillPaint(row, column));
 756:                     }
 757:                     else {
 758:                         g2.setPaint(getItemPaint(row, column));   
 759:                     }
 760:                     g2.fill(shape);
 761:                 }
 762:                 if (this.drawOutlines) {
 763:                     if (this.useOutlinePaint) {
 764:                         g2.setPaint(getItemOutlinePaint(row, column));   
 765:                     }
 766:                     else {
 767:                         g2.setPaint(getItemPaint(row, column));
 768:                     }
 769:                     g2.setStroke(getItemOutlineStroke(row, column));
 770:                     g2.draw(shape);
 771:                 }
 772:             }
 773: 
 774:             // draw the item label if there is one...
 775:             if (isItemLabelVisible(row, column)) {
 776:                 if (orientation == PlotOrientation.HORIZONTAL) {
 777:                     drawItemLabel(g2, orientation, dataset, row, column, y1, 
 778:                             x1, (value < 0.0));
 779:                 }
 780:                 else if (orientation == PlotOrientation.VERTICAL) {
 781:                     drawItemLabel(g2, orientation, dataset, row, column, x1, 
 782:                             y1, (value < 0.0));
 783:                 }
 784:             }
 785: 
 786:             // add an item entity, if this information is being collected
 787:             EntityCollection entities = state.getEntityCollection();
 788:             if (entities != null) {
 789:                 addItemEntity(entities, dataset, row, column, shape);
 790:             }
 791:         }
 792: 
 793:     }
 794:     
 795:     /**
 796:      * Tests this renderer for equality with an arbitrary object.
 797:      *
 798:      * @param obj  the object (<code>null</code> permitted).
 799:      *
 800:      * @return A boolean.
 801:      */
 802:     public boolean equals(Object obj) {
 803: 
 804:         if (obj == this) {
 805:             return true;
 806:         }
 807:         if (!(obj instanceof LineAndShapeRenderer)) {
 808:             return false;
 809:         }
 810:         
 811:         LineAndShapeRenderer that = (LineAndShapeRenderer) obj;
 812:         if (this.baseLinesVisible != that.baseLinesVisible) {
 813:             return false;
 814:         }
 815:         if (!ObjectUtilities.equal(this.seriesLinesVisible, 
 816:                 that.seriesLinesVisible)) {
 817:             return false;
 818:         }
 819:         if (!ObjectUtilities.equal(this.linesVisible, that.linesVisible)) {
 820:             return false;
 821:         }
 822:         if (this.baseShapesVisible != that.baseShapesVisible) {
 823:             return false;
 824:         }
 825:         if (!ObjectUtilities.equal(this.seriesShapesVisible, 
 826:                 that.seriesShapesVisible)) {
 827:             return false;
 828:         }
 829:         if (!ObjectUtilities.equal(this.shapesVisible, that.shapesVisible)) {
 830:             return false;
 831:         }
 832:         if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
 833:             return false;
 834:         }
 835:         if (!ObjectUtilities.equal(this.seriesShapesFilled, 
 836:                 that.seriesShapesFilled)) {
 837:             return false;
 838:         }
 839:         if (this.baseShapesFilled != that.baseShapesFilled) {
 840:             return false;
 841:         }
 842:         if (this.useOutlinePaint != that.useOutlinePaint) {
 843:             return false;
 844:         }
 845:         if (!super.equals(obj)) {
 846:             return false;
 847:         }
 848:         return true;
 849:     }
 850: 
 851:     /**
 852:      * Returns an independent copy of the renderer.
 853:      * 
 854:      * @return A clone.
 855:      * 
 856:      * @throws CloneNotSupportedException  should not happen.
 857:      */
 858:     public Object clone() throws CloneNotSupportedException {
 859:         LineAndShapeRenderer clone = (LineAndShapeRenderer) super.clone();
 860:         clone.seriesLinesVisible 
 861:             = (BooleanList) this.seriesLinesVisible.clone();
 862:         clone.seriesShapesVisible 
 863:             = (BooleanList) this.seriesLinesVisible.clone();
 864:         clone.seriesShapesFilled 
 865:             = (BooleanList) this.seriesShapesFilled.clone();
 866:         return clone;
 867:     }
 868:     
 869: }