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

   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:  * StandardXYItemRenderer.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:  *                   Jonathan Nash;
  35:  *                   Andreas Schneider;
  36:  *                   Norbert Kiesel (for TBD Networks);
  37:  *                   Christian W. Zuckschwerdt;
  38:  *                   Bill Kelemen;
  39:  *                   Nicolas Brodu (for Astrium and EADS Corporate Research 
  40:  *                   Center);
  41:  *
  42:  * $Id: StandardXYItemRenderer.java,v 1.18.2.6 2006/07/06 09:31:46 mungady Exp $
  43:  *
  44:  * Changes:
  45:  * --------
  46:  * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG);
  47:  * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
  48:  * 21-Dec-2001 : Added working line instance to improve performance (DG);
  49:  * 22-Jan-2002 : Added code to lock crosshairs to data points.  Based on code 
  50:  *               by Jonathan Nash (DG);
  51:  * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
  52:  * 28-Mar-2002 : Added a property change listener mechanism so that the 
  53:  *               renderer no longer needs to be immutable (DG);
  54:  * 02-Apr-2002 : Modified to handle null values (DG);
  55:  * 09-Apr-2002 : Modified draw method to return void.  Removed the translated 
  56:  *               zero from the drawItem method.  Override the initialise() 
  57:  *               method to calculate it (DG);
  58:  * 13-May-2002 : Added code from Andreas Schneider to allow changing 
  59:  *               shapes/colors per item (DG);
  60:  * 24-May-2002 : Incorporated tooltips into chart entities (DG);
  61:  * 25-Jun-2002 : Removed redundant code (DG);
  62:  * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA);
  63:  * 08-Aug-2002 : Added discontinuous lines option contributed by 
  64:  *               Norbert Kiesel (DG);
  65:  * 20-Aug-2002 : Added user definable default values to be returned by 
  66:  *               protected methods unless overridden by a subclass (DG);
  67:  * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG);
  68:  * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  69:  * 25-Mar-2003 : Implemented Serializable (DG);
  70:  * 01-May-2003 : Modified drawItem() method signature (DG);
  71:  * 15-May-2003 : Modified to take into account the plot orientation (DG);
  72:  * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
  73:  * 30-Jul-2003 : Modified entity constructor (CZ);
  74:  * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
  75:  * 24-Aug-2003 : Added null/NaN checks in drawItem (BK);
  76:  * 08-Sep-2003 : Fixed serialization (NB);
  77:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  78:  * 21-Jan-2004 : Override for getLegendItem() method (DG);
  79:  * 27-Jan-2004 : Moved working line into state object (DG);
  80:  * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding 
  81:  *               easier (DG);
  82:  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed 
  83:  *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
  84:  * 08-Jun-2004 : Modified to use getX() and getY() methods (DG);
  85:  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
  86:  *               getYValue() (DG);
  87:  * 25-Aug-2004 : Created addEntity() method in superclass (DG);
  88:  * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG);
  89:  * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
  90:  * 23-Feb-2005 : Fixed getLegendItem() method to show lines.  Fixed bug
  91:  *               1077108 (shape not visible for first item in series) (DG);
  92:  * 10-Apr-2005 : Fixed item label positioning with horizontal orientation (DG);
  93:  * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
  94:  * 27-Apr-2005 : Use generator for series label in legend (DG);
  95:  * 15-Jun-2006 : Fixed bug (1380480) for rendering series as path (DG); 
  96:  *
  97:  */
  98: 
  99: package org.jfree.chart.renderer.xy;
 100: 
 101: import java.awt.Graphics2D;
 102: import java.awt.Image;
 103: import java.awt.Paint;
 104: import java.awt.Point;
 105: import java.awt.Shape;
 106: import java.awt.Stroke;
 107: import java.awt.geom.GeneralPath;
 108: import java.awt.geom.Line2D;
 109: import java.awt.geom.Rectangle2D;
 110: import java.io.IOException;
 111: import java.io.ObjectInputStream;
 112: import java.io.ObjectOutputStream;
 113: import java.io.Serializable;
 114: 
 115: import org.jfree.chart.LegendItem;
 116: import org.jfree.chart.axis.ValueAxis;
 117: import org.jfree.chart.entity.EntityCollection;
 118: import org.jfree.chart.event.RendererChangeEvent;
 119: import org.jfree.chart.labels.XYToolTipGenerator;
 120: import org.jfree.chart.plot.CrosshairState;
 121: import org.jfree.chart.plot.Plot;
 122: import org.jfree.chart.plot.PlotOrientation;
 123: import org.jfree.chart.plot.PlotRenderingInfo;
 124: import org.jfree.chart.plot.XYPlot;
 125: import org.jfree.chart.urls.XYURLGenerator;
 126: import org.jfree.data.xy.XYDataset;
 127: import org.jfree.io.SerialUtilities;
 128: import org.jfree.ui.RectangleEdge;
 129: import org.jfree.util.BooleanList;
 130: import org.jfree.util.BooleanUtilities;
 131: import org.jfree.util.ObjectUtilities;
 132: import org.jfree.util.PublicCloneable;
 133: import org.jfree.util.ShapeUtilities;
 134: import org.jfree.util.UnitType;
 135: 
 136: /**
 137:  * Standard item renderer for an {@link XYPlot}.  This class can draw (a) 
 138:  * shapes at each point, or (b) lines between points, or (c) both shapes and 
 139:  * lines.
 140:  */
 141: public class StandardXYItemRenderer extends AbstractXYItemRenderer 
 142:                                     implements XYItemRenderer,
 143:                                                Cloneable,
 144:                                                PublicCloneable,
 145:                                                Serializable {
 146: 
 147:     /** For serialization. */
 148:     private static final long serialVersionUID = -3271351259436865995L;
 149:     
 150:     /** Constant for the type of rendering (shapes only). */
 151:     public static final int SHAPES = 1;
 152: 
 153:     /** Constant for the type of rendering (lines only). */
 154:     public static final int LINES = 2;
 155: 
 156:     /** Constant for the type of rendering (shapes and lines). */
 157:     public static final int SHAPES_AND_LINES = SHAPES | LINES;
 158: 
 159:     /** Constant for the type of rendering (images only). */
 160:     public static final int IMAGES = 4;
 161: 
 162:     /** Constant for the type of rendering (discontinuous lines). */
 163:     public static final int DISCONTINUOUS = 8;
 164: 
 165:     /** Constant for the type of rendering (discontinuous lines). */
 166:     public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS;
 167: 
 168:     /** A flag indicating whether or not shapes are drawn at each XY point. */
 169:     private boolean baseShapesVisible;
 170: 
 171:     /** A flag indicating whether or not lines are drawn between XY points. */
 172:     private boolean plotLines;
 173: 
 174:     /** A flag indicating whether or not images are drawn between XY points. */
 175:     private boolean plotImages;
 176: 
 177:     /** A flag controlling whether or not discontinuous lines are used. */
 178:     private boolean plotDiscontinuous;
 179: 
 180:     /** Specifies how the gap threshold value is interpreted. */
 181:     private UnitType gapThresholdType = UnitType.RELATIVE;
 182:     
 183:     /** Threshold for deciding when to discontinue a line. */
 184:     private double gapThreshold = 1.0;
 185: 
 186:     /** A flag that controls whether or not shapes are filled for ALL series. */
 187:     private Boolean shapesFilled;
 188: 
 189:     /** 
 190:      * A table of flags that control (per series) whether or not shapes are 
 191:      * filled. 
 192:      */
 193:     private BooleanList seriesShapesFilled;
 194: 
 195:     /** The default value returned by the getShapeFilled() method. */
 196:     private boolean baseShapesFilled;
 197: 
 198:     /** 
 199:      * A flag that controls whether or not each series is drawn as a single 
 200:      * path. 
 201:      */
 202:     private boolean drawSeriesLineAsPath;
 203: 
 204:     /** 
 205:      * The shape that is used to represent a line in the legend. 
 206:      * This should never be set to <code>null</code>. 
 207:      */
 208:     private transient Shape legendLine;
 209:     
 210:     /**
 211:      * Constructs a new renderer.
 212:      */
 213:     public StandardXYItemRenderer() {
 214:         this(LINES, null);
 215:     }
 216: 
 217:     /**
 218:      * Constructs a new renderer.
 219:      * <p>
 220:      * To specify the type of renderer, use one of the constants: SHAPES, LINES
 221:      * or SHAPES_AND_LINES.
 222:      *
 223:      * @param type  the type.
 224:      */
 225:     public StandardXYItemRenderer(int type) {
 226:         this(type, null);
 227:     }
 228: 
 229:     /**
 230:      * Constructs a new renderer.
 231:      * <p>
 232:      * To specify the type of renderer, use one of the constants: SHAPES, LINES
 233:      * or SHAPES_AND_LINES.
 234:      *
 235:      * @param type  the type of renderer.
 236:      * @param toolTipGenerator  the item label generator (<code>null</code> 
 237:      *                          permitted).
 238:      */
 239:     public StandardXYItemRenderer(int type, 
 240:                                   XYToolTipGenerator toolTipGenerator) {
 241:         this(type, toolTipGenerator, null);
 242:     }
 243: 
 244:     /**
 245:      * Constructs a new renderer.
 246:      * <p>
 247:      * To specify the type of renderer, use one of the constants: SHAPES, LINES 
 248:      * or SHAPES_AND_LINES.
 249:      *
 250:      * @param type  the type of renderer.
 251:      * @param toolTipGenerator  the item label generator (<code>null</code> 
 252:      *                          permitted).
 253:      * @param urlGenerator  the URL generator.
 254:      */
 255:     public StandardXYItemRenderer(int type,
 256:                                   XYToolTipGenerator toolTipGenerator,
 257:                                   XYURLGenerator urlGenerator) {
 258: 
 259:         super();
 260:         setToolTipGenerator(toolTipGenerator);
 261:         setURLGenerator(urlGenerator);
 262:         if ((type & SHAPES) != 0) {
 263:             this.baseShapesVisible = true;
 264:         }
 265:         if ((type & LINES) != 0) {
 266:             this.plotLines = true;
 267:         }
 268:         if ((type & IMAGES) != 0) {
 269:             this.plotImages = true;
 270:         }
 271:         if ((type & DISCONTINUOUS) != 0) {
 272:             this.plotDiscontinuous = true;
 273:         }
 274: 
 275:         this.shapesFilled = null;
 276:         this.seriesShapesFilled = new BooleanList();
 277:         this.baseShapesFilled = true;
 278:         this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
 279:         this.drawSeriesLineAsPath = false;
 280:     }
 281: 
 282:     /**
 283:      * Returns true if shapes are being plotted by the renderer.
 284:      *
 285:      * @return <code>true</code> if shapes are being plotted by the renderer.
 286:      */
 287:     public boolean getBaseShapesVisible() {
 288:         return this.baseShapesVisible;
 289:     }
 290: 
 291:     /**
 292:      * Sets the flag that controls whether or not a shape is plotted at each 
 293:      * data point.
 294:      *
 295:      * @param flag  the flag.
 296:      */
 297:     public void setBaseShapesVisible(boolean flag) {
 298:         if (this.baseShapesVisible != flag) {
 299:             this.baseShapesVisible = flag;
 300:             notifyListeners(new RendererChangeEvent(this));
 301:         }
 302:     }
 303: 
 304:     // SHAPES FILLED
 305: 
 306:     /**
 307:      * Returns the flag used to control whether or not the shape for an item is
 308:      * filled.
 309:      * <p>
 310:      * The default implementation passes control to the 
 311:      * <code>getSeriesShapesFilled</code> method.  You can override this method 
 312:      * if you require different behaviour.
 313:      *
 314:      * @param series  the series index (zero-based).
 315:      * @param item  the item index (zero-based).
 316:      *
 317:      * @return A boolean.
 318:      */
 319:     public boolean getItemShapeFilled(int series, int item) {
 320:         return getSeriesShapesFilled(series);
 321:     }
 322: 
 323:     /**
 324:      * Returns the flag used to control whether or not the shapes for a series
 325:      * are filled.
 326:      *
 327:      * @param series  the series index (zero-based).
 328:      *
 329:      * @return A boolean.
 330:      */
 331:     public boolean getSeriesShapesFilled(int series) {
 332: 
 333:         // return the overall setting, if there is one...
 334:         if (this.shapesFilled != null) {
 335:             return this.shapesFilled.booleanValue();
 336:         }
 337: 
 338:         // otherwise look up the paint table
 339:         Boolean flag = this.seriesShapesFilled.getBoolean(series);
 340:         if (flag != null) {
 341:             return flag.booleanValue();
 342:         }
 343:         else {
 344:             return this.baseShapesFilled;
 345:         }
 346: 
 347:     }
 348: 
 349:     /**
 350:      * Sets the 'shapes filled' for ALL series.
 351:      *
 352:      * @param filled  the flag.
 353:      */
 354:     public void setShapesFilled(boolean filled) {
 355:         // here we use BooleanUtilities to remain compatible with JDKs < 1.4 
 356:         setShapesFilled(BooleanUtilities.valueOf(filled));
 357:     }
 358: 
 359:     /**
 360:      * Sets the 'shapes filled' for ALL series.
 361:      *
 362:      * @param filled  the flag (<code>null</code> permitted).
 363:      */
 364:     public void setShapesFilled(Boolean filled) {
 365:         this.shapesFilled = filled;
 366:     }
 367: 
 368:     /**
 369:      * Sets the 'shapes filled' flag for a series.
 370:      *
 371:      * @param series  the series index (zero-based).
 372:      * @param flag  the flag.
 373:      */
 374:     public void setSeriesShapesFilled(int series, Boolean flag) {
 375:         this.seriesShapesFilled.setBoolean(series, flag);
 376:     }
 377: 
 378:     /**
 379:      * Returns the base 'shape filled' attribute.
 380:      *
 381:      * @return The base flag.
 382:      */
 383:     public boolean getBaseShapesFilled() {
 384:         return this.baseShapesFilled;
 385:     }
 386: 
 387:     /**
 388:      * Sets the base 'shapes filled' flag.
 389:      *
 390:      * @param flag  the flag.
 391:      */
 392:     public void setBaseShapesFilled(boolean flag) {
 393:         this.baseShapesFilled = flag;
 394:     }
 395: 
 396:     /**
 397:      * Returns true if lines are being plotted by the renderer.
 398:      *
 399:      * @return <code>true</code> if lines are being plotted by the renderer.
 400:      */
 401:     public boolean getPlotLines() {
 402:         return this.plotLines;
 403:     }
 404: 
 405:     /**
 406:      * Sets the flag that controls whether or not a line is plotted between 
 407:      * each data point.
 408:      *
 409:      * @param flag  the flag.
 410:      */
 411:     public void setPlotLines(boolean flag) {
 412:         if (this.plotLines != flag) {
 413:             this.plotLines = flag;
 414:             notifyListeners(new RendererChangeEvent(this));
 415:         }
 416:     }
 417: 
 418:     /**
 419:      * Returns the gap threshold type (relative or absolute).
 420:      * 
 421:      * @return The type.
 422:      */
 423:     public UnitType getGapThresholdType() {
 424:         return this.gapThresholdType;
 425:     }
 426:     
 427:     /**
 428:      * Sets the gap threshold type.
 429:      * 
 430:      * @param thresholdType  the type (<code>null</code> not permitted).
 431:      */
 432:     public void setGapThresholdType(UnitType thresholdType) {
 433:         if (thresholdType == null) {
 434:             throw new IllegalArgumentException(
 435:                     "Null 'thresholdType' argument.");
 436:         }
 437:         this.gapThresholdType = thresholdType;
 438:         notifyListeners(new RendererChangeEvent(this));
 439:     }
 440:     
 441:     /**
 442:      * Returns the gap threshold for discontinuous lines.
 443:      *
 444:      * @return The gap threshold.
 445:      */
 446:     public double getGapThreshold() {
 447:         return this.gapThreshold;
 448:     }
 449: 
 450:     /**
 451:      * Sets the gap threshold for discontinuous lines.
 452:      *
 453:      * @param t  the threshold.
 454:      */
 455:     public void setGapThreshold(double t) {
 456:         this.gapThreshold = t;
 457:         notifyListeners(new RendererChangeEvent(this));
 458:     }
 459: 
 460:     /**
 461:      * Returns true if images are being plotted by the renderer.
 462:      *
 463:      * @return <code>true</code> if images are being plotted by the renderer.
 464:      */
 465:     public boolean getPlotImages() {
 466:         return this.plotImages;
 467:     }
 468: 
 469:     /**
 470:      * Sets the flag that controls whether or not an image is drawn at each 
 471:      * data point.
 472:      *
 473:      * @param flag  the flag.
 474:      */
 475:     public void setPlotImages(boolean flag) {
 476:         if (this.plotImages != flag) {
 477:             this.plotImages = flag;
 478:             notifyListeners(new RendererChangeEvent(this));
 479:         }
 480:     }
 481: 
 482:     /**
 483:      * Returns true if lines should be discontinuous.
 484:      *
 485:      * @return <code>true</code> if lines should be discontinuous.
 486:      */
 487:     public boolean getPlotDiscontinuous() {
 488:         return this.plotDiscontinuous;
 489:     }
 490: 
 491:     /**
 492:      * Returns a flag that controls whether or not each series is drawn as a 
 493:      * single path.
 494:      * 
 495:      * @return A boolean.
 496:      */
 497:     public boolean getDrawSeriesLineAsPath() {
 498:         return this.drawSeriesLineAsPath;
 499:     }
 500:     
 501:     /**
 502:      * Sets the flag that controls whether or not each series is drawn as a 
 503:      * single path.
 504:      * 
 505:      * @param flag  the flag.
 506:      */
 507:     public void setDrawSeriesLineAsPath(boolean flag) {
 508:         this.drawSeriesLineAsPath = flag;
 509:     }
 510:     
 511:     /**
 512:      * Returns the shape used to represent a line in the legend.
 513:      * 
 514:      * @return The legend line (never <code>null</code>).
 515:      */
 516:     public Shape getLegendLine() {
 517:         return this.legendLine;   
 518:     }
 519:     
 520:     /**
 521:      * Sets the shape used as a line in each legend item and sends a 
 522:      * {@link RendererChangeEvent} to all registered listeners.
 523:      * 
 524:      * @param line  the line (<code>null</code> not permitted).
 525:      */
 526:     public void setLegendLine(Shape line) {
 527:         if (line == null) {
 528:             throw new IllegalArgumentException("Null 'line' argument.");   
 529:         }
 530:         this.legendLine = line;
 531:         notifyListeners(new RendererChangeEvent(this));
 532:     }
 533: 
 534:     /**
 535:      * Returns a legend item for a series.
 536:      *
 537:      * @param datasetIndex  the dataset index (zero-based).
 538:      * @param series  the series index (zero-based).
 539:      *
 540:      * @return A legend item for the series.
 541:      */
 542:     public LegendItem getLegendItem(int datasetIndex, int series) {
 543:         XYPlot plot = getPlot();
 544:         if (plot == null) {
 545:             return null;
 546:         }
 547:         LegendItem result = null;
 548:         XYDataset dataset = plot.getDataset(datasetIndex);
 549:         if (dataset != null) {
 550:             if (getItemVisible(series, 0)) {
 551:                 String label = getLegendItemLabelGenerator().generateLabel(
 552:                         dataset, series);
 553:                 String description = label;
 554:                 String toolTipText = null;
 555:                 if (getLegendItemToolTipGenerator() != null) {
 556:                     toolTipText = getLegendItemToolTipGenerator().generateLabel(
 557:                             dataset, series);
 558:                 }
 559:                 String urlText = null;
 560:                 if (getLegendItemURLGenerator() != null) {
 561:                     urlText = getLegendItemURLGenerator().generateLabel(
 562:                             dataset, series);
 563:                 }
 564:                 Shape shape = getSeriesShape(series);
 565:                 boolean shapeFilled = getSeriesShapesFilled(series);
 566:                 Paint paint = getSeriesPaint(series);
 567:                 Paint linePaint = paint;
 568:                 Stroke lineStroke = getSeriesStroke(series);
 569:                 result = new LegendItem(label, description, toolTipText, 
 570:                         urlText, this.baseShapesVisible, shape, shapeFilled,
 571:                         paint, !shapeFilled, paint, lineStroke, 
 572:                         this.plotLines, this.legendLine, lineStroke, linePaint);
 573:             }
 574:         }
 575:         return result;
 576:     }
 577: 
 578:     /**
 579:      * Records the state for the renderer.  This is used to preserve state 
 580:      * information between calls to the drawItem() method for a single chart 
 581:      * drawing.
 582:      */
 583:     public static class State extends XYItemRendererState {
 584:         
 585:         /** The path for the current series. */
 586:         public GeneralPath seriesPath;
 587:         
 588:         /** The series index. */
 589:         private int seriesIndex;
 590:         
 591:         /** 
 592:          * A flag that indicates if the last (x, y) point was 'good' 
 593:          * (non-null). 
 594:          */
 595:         private boolean lastPointGood;
 596:         
 597:         /**
 598:          * Creates a new state instance.
 599:          * 
 600:          * @param info  the plot rendering info.
 601:          */
 602:         public State(PlotRenderingInfo info) {
 603:             super(info);
 604:         }
 605:         
 606:         /**
 607:          * Returns a flag that indicates if the last point drawn (in the 
 608:          * current series) was 'good' (non-null).
 609:          * 
 610:          * @return A boolean.
 611:          */
 612:         public boolean isLastPointGood() {
 613:             return this.lastPointGood;
 614:         }
 615:         
 616:         /**
 617:          * Sets a flag that indicates if the last point drawn (in the current 
 618:          * series) was 'good' (non-null).
 619:          * 
 620:          * @param good  the flag.
 621:          */
 622:         public void setLastPointGood(boolean good) {
 623:             this.lastPointGood = good;
 624:         }
 625:         
 626:         /**
 627:          * Returns the series index for the current path.
 628:          * 
 629:          * @return The series index for the current path.
 630:          */
 631:         public int getSeriesIndex() {
 632:             return seriesIndex;
 633:         }
 634:         
 635:         /**
 636:          * Sets the series index for the current path.
 637:          * 
 638:          * @param index  the index.
 639:          */
 640:         public void setSeriesIndex(int index) {
 641:             this.seriesIndex = index;
 642:         }
 643:     }
 644:     
 645:     /**
 646:      * Initialises the renderer.
 647:      * <P>
 648:      * This method will be called before the first item is rendered, giving the
 649:      * renderer an opportunity to initialise any state information it wants to 
 650:      * maintain. The renderer can do nothing if it chooses.
 651:      *
 652:      * @param g2  the graphics device.
 653:      * @param dataArea  the area inside the axes.
 654:      * @param plot  the plot.
 655:      * @param data  the data.
 656:      * @param info  an optional info collection object to return data back to 
 657:      *              the caller.
 658:      *
 659:      * @return The renderer state.
 660:      */
 661:     public XYItemRendererState initialise(Graphics2D g2,
 662:                                           Rectangle2D dataArea,
 663:                                           XYPlot plot,
 664:                                           XYDataset data,
 665:                                           PlotRenderingInfo info) {
 666: 
 667:         State state = new State(info);
 668:         state.seriesPath = new GeneralPath();
 669:         state.seriesIndex = -1;
 670:         return state;
 671: 
 672:     }
 673:     
 674:     /**
 675:      * Draws the visual representation of a single data item.
 676:      *
 677:      * @param g2  the graphics device.
 678:      * @param state  the renderer state.
 679:      * @param dataArea  the area within which the data is being drawn.
 680:      * @param info  collects information about the drawing.
 681:      * @param plot  the plot (can be used to obtain standard color information 
 682:      *              etc).
 683:      * @param domainAxis  the domain axis.
 684:      * @param rangeAxis  the range axis.
 685:      * @param dataset  the dataset.
 686:      * @param series  the series index (zero-based).
 687:      * @param item  the item index (zero-based).
 688:      * @param crosshairState  crosshair information for the plot 
 689:      *                        (<code>null</code> permitted).
 690:      * @param pass  the pass index.
 691:      */
 692:     public void drawItem(Graphics2D g2, 
 693:                          XYItemRendererState state,
 694:                          Rectangle2D dataArea, 
 695:                          PlotRenderingInfo info, 
 696:                          XYPlot plot,
 697:                          ValueAxis domainAxis, 
 698:                          ValueAxis rangeAxis, 
 699:                          XYDataset dataset,
 700:                          int series, 
 701:                          int item, 
 702:                          CrosshairState crosshairState, 
 703:                          int pass) {
 704: 
 705:         boolean itemVisible = getItemVisible(series, item);
 706:         
 707:         // setup for collecting optional entity info...
 708:         Shape entityArea = null;
 709:         EntityCollection entities = null;
 710:         if (info != null) {
 711:             entities = info.getOwner().getEntityCollection();
 712:         }
 713: 
 714:         PlotOrientation orientation = plot.getOrientation();
 715:         Paint paint = getItemPaint(series, item);
 716:         Stroke seriesStroke = getItemStroke(series, item);
 717:         g2.setPaint(paint);
 718:         g2.setStroke(seriesStroke);
 719: 
 720:         // get the data point...
 721:         double x1 = dataset.getXValue(series, item);
 722:         double y1 = dataset.getYValue(series, item);
 723:         if (Double.isNaN(x1) || Double.isNaN(y1)) {
 724:             itemVisible = false;
 725:         }
 726: 
 727:         RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
 728:         RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
 729:         double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
 730:         double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
 731: 
 732:         if (getPlotLines()) {
 733:             if (this.drawSeriesLineAsPath) {
 734:                 State s = (State) state;
 735:                 if (s.getSeriesIndex() != series) {
 736:                     // we are starting a new series path
 737:                     s.seriesPath.reset();
 738:                     s.lastPointGood = false;
 739:                     s.setSeriesIndex(series);
 740:                 }
 741:                 
 742:                 // update path to reflect latest point
 743:                 if (itemVisible && !Double.isNaN(transX1) 
 744:                         && !Double.isNaN(transY1)) {
 745:                     float x = (float) transX1;
 746:                     float y = (float) transY1;
 747:                     if (orientation == PlotOrientation.HORIZONTAL) {
 748:                         x = (float) transY1;
 749:                         y = (float) transX1;
 750:                     }
 751:                     if (s.isLastPointGood()) {
 752:                         // TODO: check threshold
 753:                         s.seriesPath.lineTo(x, y);
 754:                     }
 755:                     else {
 756:                         s.seriesPath.moveTo(x, y);
 757:                     }
 758:                     s.setLastPointGood(true);
 759:                 }
 760:                 else {
 761:                     s.setLastPointGood(false);
 762:                 }
 763:                 if (item == dataset.getItemCount(series) - 1) {
 764:                     if (s.seriesIndex == series) {
 765:                         // draw path
 766:                         g2.setStroke(getSeriesStroke(series));
 767:                         g2.setPaint(getSeriesPaint(series));
 768:                         g2.draw(s.seriesPath);
 769:                     }
 770:                 }
 771:             }
 772: 
 773:             else if (item != 0 && itemVisible) {
 774:                 // get the previous data point...
 775:                 double x0 = dataset.getXValue(series, item - 1);
 776:                 double y0 = dataset.getYValue(series, item - 1);
 777:                 if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
 778:                     boolean drawLine = true;
 779:                     if (getPlotDiscontinuous()) {
 780:                         // only draw a line if the gap between the current and 
 781:                         // previous data point is within the threshold
 782:                         int numX = dataset.getItemCount(series);
 783:                         double minX = dataset.getXValue(series, 0);
 784:                         double maxX = dataset.getXValue(series, numX - 1);
 785:                         if (this.gapThresholdType == UnitType.ABSOLUTE) {
 786:                             drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
 787:                         }
 788:                         else {
 789:                             drawLine = Math.abs(x1 - x0) <= ((maxX - minX) 
 790:                                 / numX * getGapThreshold());
 791:                         }
 792:                     }
 793:                     if (drawLine) {
 794:                         double transX0 = domainAxis.valueToJava2D(x0, dataArea,
 795:                                 xAxisLocation);
 796:                         double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
 797:                                 yAxisLocation);
 798: 
 799:                         // only draw if we have good values
 800:                         if (Double.isNaN(transX0) || Double.isNaN(transY0) 
 801:                             || Double.isNaN(transX1) || Double.isNaN(transY1)) {
 802:                             return;
 803:                         }
 804: 
 805:                         if (orientation == PlotOrientation.HORIZONTAL) {
 806:                             state.workingLine.setLine(transY0, transX0, 
 807:                                     transY1, transX1);
 808:                         }
 809:                         else if (orientation == PlotOrientation.VERTICAL) {
 810:                             state.workingLine.setLine(transX0, transY0, 
 811:                                     transX1, transY1);
 812:                         }
 813: 
 814:                         if (state.workingLine.intersects(dataArea)) {
 815:                             g2.draw(state.workingLine);
 816:                         }
 817:                     }
 818:                 }
 819:             }
 820:         }
 821:         
 822:         // we needed to get this far even for invisible items, to ensure that
 823:         // seriesPath updates happened, but now there is nothing more we need
 824:         // to do for non-visible items...
 825:         if (!itemVisible) {
 826:             return;
 827:         }
 828: 
 829:         if (getBaseShapesVisible()) {
 830: 
 831:             Shape shape = getItemShape(series, item);
 832:             if (orientation == PlotOrientation.HORIZONTAL) {
 833:                 shape = ShapeUtilities.createTranslatedShape(shape, transY1, 
 834:                         transX1);
 835:             }
 836:             else if (orientation == PlotOrientation.VERTICAL) {
 837:                 shape = ShapeUtilities.createTranslatedShape(shape, transX1, 
 838:                         transY1);
 839:             }
 840:             if (shape.intersects(dataArea)) {
 841:                 if (getItemShapeFilled(series, item)) {
 842:                     g2.fill(shape);
 843:                 }
 844:                 else {
 845:                     g2.draw(shape);
 846:                 }
 847:             }
 848:             entityArea = shape;
 849: 
 850:         }
 851: 
 852:         if (getPlotImages()) {
 853:             Image image = getImage(plot, series, item, transX1, transY1);
 854:             if (image != null) {
 855:                 Point hotspot = getImageHotspot(plot, series, item, transX1, 
 856:                         transY1, image);
 857:                 g2.drawImage(image, (int) (transX1 - hotspot.getX()), 
 858:                         (int) (transY1 - hotspot.getY()), null);
 859:                 entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(), 
 860:                         transY1 - hotspot.getY(), image.getWidth(null), 
 861:                         image.getHeight(null));
 862:             }
 863: 
 864:         }
 865: 
 866:         // draw the item label if there is one...
 867:         if (isItemLabelVisible(series, item)) {
 868:             double xx = transX1;
 869:             double yy = transY1;
 870:             if (orientation == PlotOrientation.HORIZONTAL) {
 871:                 xx = transY1;
 872:                 yy = transX1;
 873:             }          
 874:             drawItemLabel(g2, orientation, dataset, series, item, xx, yy, 
 875:                     (y1 < 0.0));
 876:         }
 877: 
 878:         updateCrosshairValues(crosshairState, x1, y1, transX1, transY1, 
 879:                 orientation);
 880: 
 881:         // add an entity for the item...
 882:         if (entities != null) {
 883:             addEntity(entities, entityArea, dataset, series, item, 
 884:                     transX1, transY1);
 885:         }
 886: 
 887:     }
 888: 
 889:     /**
 890:      * Tests this renderer for equality with another object.
 891:      *
 892:      * @param obj  the object (<code>null</code> permitted).
 893:      *
 894:      * @return A boolean.
 895:      */
 896:     public boolean equals(Object obj) {
 897: 
 898:         if (obj == this) {
 899:             return true;
 900:         }
 901:         if (!(obj instanceof StandardXYItemRenderer)) {
 902:             return false;
 903:         }
 904:         if (!super.equals(obj)) {
 905:             return false;
 906:         }
 907:         StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
 908:         if (this.baseShapesVisible != that.baseShapesVisible) {
 909:             return false;
 910:         }
 911:         if (this.plotLines != that.plotLines) {
 912:             return false;
 913:         }
 914:         if (this.plotImages != that.plotImages) {
 915:             return false;
 916:         }
 917:         if (this.plotDiscontinuous != that.plotDiscontinuous) {
 918:             return false;
 919:         }
 920:         if (this.gapThresholdType != that.gapThresholdType) {
 921:             return false;
 922:         }
 923:         if (this.gapThreshold != that.gapThreshold) {
 924:             return false;
 925:         }
 926:         if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
 927:             return false;
 928:         }
 929:         if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
 930:             return false;   
 931:         }
 932:         return true;
 933: 
 934:     }
 935: 
 936:     /**
 937:      * Returns a clone of the renderer.
 938:      *
 939:      * @return A clone.
 940:      *
 941:      * @throws CloneNotSupportedException  if the renderer cannot be cloned.
 942:      */
 943:     public Object clone() throws CloneNotSupportedException {
 944:         return super.clone();
 945:     }
 946: 
 947:     ////////////////////////////////////////////////////////////////////////////
 948:     // PROTECTED METHODS
 949:     // These provide the opportunity to subclass the standard renderer and 
 950:     // create custom effects.
 951:     ////////////////////////////////////////////////////////////////////////////
 952: 
 953:     /**
 954:      * Returns the image used to draw a single data item.
 955:      *
 956:      * @param plot  the plot (can be used to obtain standard color information 
 957:      *              etc).
 958:      * @param series  the series index.
 959:      * @param item  the item index.
 960:      * @param x  the x value of the item.
 961:      * @param y  the y value of the item.
 962:      *
 963:      * @return The image.
 964:      */
 965:     protected Image getImage(Plot plot, int series, int item, 
 966:                              double x, double y) {
 967:         // should this be added to the plot as well ?
 968:         // return plot.getShape(series, item, x, y, scale);
 969:         // or should this be left to the user - like this:
 970:         return null;
 971:     }
 972: 
 973:     /**
 974:      * Returns the hotspot of the image used to draw a single data item.
 975:      * The hotspot is the point relative to the top left of the image
 976:      * that should indicate the data item. The default is the center of the
 977:      * image.
 978:      *
 979:      * @param plot  the plot (can be used to obtain standard color information 
 980:      *              etc).
 981:      * @param image  the image (can be used to get size information about the 
 982:      *               image)
 983:      * @param series  the series index
 984:      * @param item  the item index
 985:      * @param x  the x value of the item
 986:      * @param y  the y value of the item
 987:      *
 988:      * @return The hotspot used to draw the data item.
 989:      */
 990:     protected Point getImageHotspot(Plot plot, int series, int item,
 991:                                     double x, double y, Image image) {
 992: 
 993:         int height = image.getHeight(null);
 994:         int width = image.getWidth(null);
 995:         return new Point(width / 2, height / 2);
 996: 
 997:     }
 998:     
 999:     /**
1000:      * Provides serialization support.
1001:      *
1002:      * @param stream  the input stream.
1003:      *
1004:      * @throws IOException  if there is an I/O error.
1005:      * @throws ClassNotFoundException  if there is a classpath problem.
1006:      */
1007:     private void readObject(ObjectInputStream stream) 
1008:             throws IOException, ClassNotFoundException {
1009:         stream.defaultReadObject();
1010:         this.legendLine = SerialUtilities.readShape(stream);
1011:     }
1012:     
1013:     /**
1014:      * Provides serialization support.
1015:      *
1016:      * @param stream  the output stream.
1017:      *
1018:      * @throws IOException  if there is an I/O error.
1019:      */
1020:     private void writeObject(ObjectOutputStream stream) throws IOException {
1021:         stream.defaultWriteObject();
1022:         SerialUtilities.writeShape(this.legendLine, stream);
1023:     }
1024: }