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