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