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

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, 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:  * AbstractXYItemRenderer.java
  29:  * ---------------------------
  30:  * (C) Copyright 2002-2007, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Richard Atkinson;
  34:  *                   Focus Computer Services Limited;
  35:  *                   Tim Bardzil;
  36:  *                   Sergei Ivanov;
  37:  *
  38:  * $Id: AbstractXYItemRenderer.java,v 1.26.2.15 2007/03/23 13:43:46 mungady Exp $
  39:  *
  40:  * Changes:
  41:  * --------
  42:  * 15-Mar-2002 : Version 1 (DG);
  43:  * 09-Apr-2002 : Added a getToolTipGenerator() method reflecting the change in
  44:  *               the XYItemRenderer interface (DG);
  45:  * 05-Aug-2002 : Added a urlGenerator member variable to support HTML image
  46:  *               maps (RA);
  47:  * 20-Aug-2002 : Added property change events for the tooltip and URL
  48:  *               generators (DG);
  49:  * 22-Aug-2002 : Moved property change support into AbstractRenderer class (DG);
  50:  * 23-Sep-2002 : Fixed errors reported by Checkstyle tool (DG);
  51:  * 18-Nov-2002 : Added methods for drawing grid lines (DG);
  52:  * 17-Jan-2003 : Moved plot classes into a separate package (DG);
  53:  * 25-Mar-2003 : Implemented Serializable (DG);
  54:  * 01-May-2003 : Modified initialise() return type and drawItem() method
  55:  *               signature (DG);
  56:  * 15-May-2003 : Modified to take into account the plot orientation (DG);
  57:  * 21-May-2003 : Added labels to markers (DG);
  58:  * 05-Jun-2003 : Added domain and range grid bands (sponsored by Focus Computer
  59:  *               Services Ltd) (DG);
  60:  * 27-Jul-2003 : Added getRangeType() to support stacked XY area charts (RA);
  61:  * 31-Jul-2003 : Deprecated all but the default constructor (DG);
  62:  * 13-Aug-2003 : Implemented Cloneable (DG);
  63:  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
  64:  * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
  65:  * 05-Nov-2003 : Fixed marker rendering bug (833623) (DG);
  66:  * 11-Feb-2004 : Updated labelling for markers (DG);
  67:  * 25-Feb-2004 : Added updateCrosshairValues() method.  Moved deprecated code
  68:  *               to bottom of source file (DG);
  69:  * 16-Apr-2004 : Added support for IntervalMarker in drawRangeMarker() method
  70:  *               - thanks to Tim Bardzil (DG);
  71:  * 05-May-2004 : Fixed bug (948310) where interval markers extend beyond axis
  72:  *               range (DG);
  73:  * 03-Jun-2004 : Fixed more bugs in drawing interval markers (DG);
  74:  * 26-Aug-2004 : Added the addEntity() method (DG);
  75:  * 29-Sep-2004 : Added annotation support (with layers) (DG);
  76:  * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities -->
  77:  *               TextUtilities (DG);
  78:  * 06-Oct-2004 : Added findDomainBounds() method and renamed
  79:  *               getRangeExtent() --> findRangeBounds() (DG);
  80:  * 07-Jan-2005 : Removed deprecated code (DG);
  81:  * 27-Jan-2005 : Modified getLegendItem() to omit hidden series (DG);
  82:  * 24-Feb-2005 : Added getLegendItems() method (DG);
  83:  * 08-Mar-2005 : Fixed positioning of marker labels (DG);
  84:  * 20-Apr-2005 : Renamed XYLabelGenerator --> XYItemLabelGenerator and
  85:  *               added generators for legend labels, tooltips and URLs (DG);
  86:  * 01-Jun-2005 : Handle one dimension of the marker label adjustment
  87:  *               automatically (DG);
  88:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  89:  * 20-Jul-2006 : Set dataset and series indices in LegendItem (DG);
  90:  * 24-Oct-2006 : Respect alpha setting in markers (see patch 1567843 by Sergei
  91:  *               Ivanov) (DG);
  92:  * 24-Oct-2006 : Added code to draw outlines for interval markers (DG);
  93:  * 24-Nov-2006 : Fixed cloning for legend item generators (DG);
  94:  * 06-Feb-2007 : Added new updateCrosshairValues() method that takes into
  95:  *               account multiple axis plots (see bug 1086307) (DG);
  96:  * 20-Feb-2007 : Fixed equals() method implementation (DG);
  97:  * 01-Mar-2007 : Fixed interval marker drawing (patch 1670686 thanks to 
  98:  *               Sergei Ivanov) (DG);
  99:  * 22-Mar-2007 : Modified the tool tip generator look up (DG);
 100:  * 23-Mar-2007 : Added drawDomainLine() method (DG);
 101:  *
 102:  */
 103: 
 104: package org.jfree.chart.renderer.xy;
 105: 
 106: import java.awt.AlphaComposite;
 107: import java.awt.Composite;
 108: import java.awt.Font;
 109: import java.awt.GradientPaint;
 110: import java.awt.Graphics2D;
 111: import java.awt.Paint;
 112: import java.awt.Shape;
 113: import java.awt.Stroke;
 114: import java.awt.geom.Ellipse2D;
 115: import java.awt.geom.Line2D;
 116: import java.awt.geom.Point2D;
 117: import java.awt.geom.Rectangle2D;
 118: import java.io.Serializable;
 119: import java.util.Iterator;
 120: import java.util.List;
 121: 
 122: import org.jfree.chart.LegendItem;
 123: import org.jfree.chart.LegendItemCollection;
 124: import org.jfree.chart.annotations.XYAnnotation;
 125: import org.jfree.chart.axis.ValueAxis;
 126: import org.jfree.chart.entity.EntityCollection;
 127: import org.jfree.chart.entity.XYItemEntity;
 128: import org.jfree.chart.event.RendererChangeEvent;
 129: import org.jfree.chart.labels.ItemLabelPosition;
 130: import org.jfree.chart.labels.StandardXYSeriesLabelGenerator;
 131: import org.jfree.chart.labels.XYItemLabelGenerator;
 132: import org.jfree.chart.labels.XYSeriesLabelGenerator;
 133: import org.jfree.chart.labels.XYToolTipGenerator;
 134: import org.jfree.chart.plot.CrosshairState;
 135: import org.jfree.chart.plot.DrawingSupplier;
 136: import org.jfree.chart.plot.IntervalMarker;
 137: import org.jfree.chart.plot.Marker;
 138: import org.jfree.chart.plot.Plot;
 139: import org.jfree.chart.plot.PlotOrientation;
 140: import org.jfree.chart.plot.PlotRenderingInfo;
 141: import org.jfree.chart.plot.ValueMarker;
 142: import org.jfree.chart.plot.XYPlot;
 143: import org.jfree.chart.renderer.AbstractRenderer;
 144: import org.jfree.chart.urls.XYURLGenerator;
 145: import org.jfree.data.Range;
 146: import org.jfree.data.general.DatasetUtilities;
 147: import org.jfree.data.xy.XYDataset;
 148: import org.jfree.text.TextUtilities;
 149: import org.jfree.ui.GradientPaintTransformer;
 150: import org.jfree.ui.Layer;
 151: import org.jfree.ui.LengthAdjustmentType;
 152: import org.jfree.ui.RectangleAnchor;
 153: import org.jfree.ui.RectangleInsets;
 154: import org.jfree.util.ObjectList;
 155: import org.jfree.util.ObjectUtilities;
 156: import org.jfree.util.PublicCloneable;
 157: 
 158: /**
 159:  * A base class that can be used to create new {@link XYItemRenderer}
 160:  * implementations.
 161:  */
 162: public abstract class AbstractXYItemRenderer extends AbstractRenderer
 163:                                              implements XYItemRenderer,
 164:                                                         Cloneable,
 165:                                                         Serializable {
 166: 
 167:     /** For serialization. */
 168:     private static final long serialVersionUID = 8019124836026607990L;
 169: 
 170:     /** The plot. */
 171:     private XYPlot plot;
 172: 
 173:     /** The item label generator for ALL series. */
 174:     private XYItemLabelGenerator itemLabelGenerator;
 175: 
 176:     /** A list of item label generators (one per series). */
 177:     private ObjectList itemLabelGeneratorList;
 178: 
 179:     /** The base item label generator. */
 180:     private XYItemLabelGenerator baseItemLabelGenerator;
 181: 
 182:     /** The tool tip generator for ALL series. */
 183:     private XYToolTipGenerator toolTipGenerator;
 184: 
 185:     /** A list of tool tip generators (one per series). */
 186:     private ObjectList toolTipGeneratorList;
 187: 
 188:     /** The base tool tip generator. */
 189:     private XYToolTipGenerator baseToolTipGenerator;
 190: 
 191:     /** The URL text generator. */
 192:     private XYURLGenerator urlGenerator;
 193: 
 194:     /**
 195:      * Annotations to be drawn in the background layer ('underneath' the data
 196:      * items).
 197:      */
 198:     private List backgroundAnnotations;
 199: 
 200:     /**
 201:      * Annotations to be drawn in the foreground layer ('on top' of the data
 202:      * items).
 203:      */
 204:     private List foregroundAnnotations;
 205: 
 206:     /** The default radius for the entity 'hotspot' */
 207:     private int defaultEntityRadius;
 208: 
 209:     /** The legend item label generator. */
 210:     private XYSeriesLabelGenerator legendItemLabelGenerator;
 211: 
 212:     /** The legend item tool tip generator. */
 213:     private XYSeriesLabelGenerator legendItemToolTipGenerator;
 214: 
 215:     /** The legend item URL generator. */
 216:     private XYSeriesLabelGenerator legendItemURLGenerator;
 217: 
 218:     /**
 219:      * Creates a renderer where the tooltip generator and the URL generator are
 220:      * both <code>null</code>.
 221:      */
 222:     protected AbstractXYItemRenderer() {
 223:         this.itemLabelGenerator = null;
 224:         this.itemLabelGeneratorList = new ObjectList();
 225:         this.toolTipGenerator = null;
 226:         this.toolTipGeneratorList = new ObjectList();
 227:         this.urlGenerator = null;
 228:         this.backgroundAnnotations = new java.util.ArrayList();
 229:         this.foregroundAnnotations = new java.util.ArrayList();
 230:         this.defaultEntityRadius = 3;
 231:         this.legendItemLabelGenerator = new StandardXYSeriesLabelGenerator(
 232:                 "{0}");
 233:     }
 234: 
 235:     /**
 236:      * Returns the number of passes through the data that the renderer requires
 237:      * in order to draw the chart.  Most charts will require a single pass, but
 238:      * some require two passes.
 239:      *
 240:      * @return The pass count.
 241:      */
 242:     public int getPassCount() {
 243:         return 1;
 244:     }
 245: 
 246:     /**
 247:      * Returns the plot that the renderer is assigned to.
 248:      *
 249:      * @return The plot (possibly <code>null</code>).
 250:      */
 251:     public XYPlot getPlot() {
 252:         return this.plot;
 253:     }
 254: 
 255:     /**
 256:      * Sets the plot that the renderer is assigned to.
 257:      *
 258:      * @param plot  the plot (<code>null</code> permitted).
 259:      */
 260:     public void setPlot(XYPlot plot) {
 261:         this.plot = plot;
 262:     }
 263: 
 264:     /**
 265:      * Initialises the renderer and returns a state object that should be
 266:      * passed to all subsequent calls to the drawItem() method.
 267:      * <P>
 268:      * This method will be called before the first item is rendered, giving the
 269:      * renderer an opportunity to initialise any state information it wants to
 270:      * maintain.  The renderer can do nothing if it chooses.
 271:      *
 272:      * @param g2  the graphics device.
 273:      * @param dataArea  the area inside the axes.
 274:      * @param plot  the plot.
 275:      * @param data  the data.
 276:      * @param info  an optional info collection object to return data back to
 277:      *              the caller.
 278:      *
 279:      * @return The renderer state (never <code>null</code>).
 280:      */
 281:     public XYItemRendererState initialise(Graphics2D g2,
 282:                                           Rectangle2D dataArea,
 283:                                           XYPlot plot,
 284:                                           XYDataset data,
 285:                                           PlotRenderingInfo info) {
 286: 
 287:         XYItemRendererState state = new XYItemRendererState(info);
 288:         return state;
 289: 
 290:     }
 291: 
 292:     // ITEM LABEL GENERATOR
 293: 
 294:     /**
 295:      * Returns the label generator for a data item.  This implementation simply
 296:      * passes control to the {@link #getSeriesItemLabelGenerator(int)} method.
 297:      * If, for some reason, you want a different generator for individual
 298:      * items, you can override this method.
 299:      *
 300:      * @param series  the series index (zero based).
 301:      * @param item  the item index (zero based).
 302:      *
 303:      * @return The generator (possibly <code>null</code>).
 304:      */
 305:     public XYItemLabelGenerator getItemLabelGenerator(int series, int item) {
 306:         // return the generator for ALL series, if there is one...
 307:         if (this.itemLabelGenerator != null) {
 308:             return this.itemLabelGenerator;
 309:         }
 310: 
 311:         // otherwise look up the generator table
 312:         XYItemLabelGenerator generator
 313:             = (XYItemLabelGenerator) this.itemLabelGeneratorList.get(series);
 314:         if (generator == null) {
 315:             generator = this.baseItemLabelGenerator;
 316:         }
 317:         return generator;
 318:     }
 319: 
 320:     /**
 321:      * Returns the item label generator for a series.
 322:      *
 323:      * @param series  the series index (zero based).
 324:      *
 325:      * @return The generator (possibly <code>null</code>).
 326:      */
 327:     public XYItemLabelGenerator getSeriesItemLabelGenerator(int series) {
 328:         return (XYItemLabelGenerator) this.itemLabelGeneratorList.get(series);
 329:     }
 330: 
 331:     /**
 332:      * Returns the item label generator override.
 333:      * 
 334:      * @return The generator (possibly <code>null</code>).
 335:      * 
 336:      * @since 1.0.5
 337:      * 
 338:      * @see #setItemLabelGenerator(XYItemLabelGenerator)
 339:      */
 340:     public XYItemLabelGenerator getItemLabelGenerator() {
 341:         return this.itemLabelGenerator;    
 342:     }
 343:     
 344:     /**
 345:      * Sets the item label generator for ALL series and sends a
 346:      * {@link RendererChangeEvent} to all registered listeners.
 347:      *
 348:      * @param generator  the generator (<code>null</code> permitted).
 349:      * 
 350:      * @see #getItemLabelGenerator()
 351:      */
 352:     public void setItemLabelGenerator(XYItemLabelGenerator generator) {
 353:         this.itemLabelGenerator = generator;
 354:         notifyListeners(new RendererChangeEvent(this));
 355:     }
 356: 
 357:     /**
 358:      * Sets the item label generator for a series and sends a
 359:      * {@link RendererChangeEvent} to all registered listeners.
 360:      *
 361:      * @param series  the series index (zero based).
 362:      * @param generator  the generator (<code>null</code> permitted).
 363:      */
 364:     public void setSeriesItemLabelGenerator(int series,
 365:                                             XYItemLabelGenerator generator) {
 366:         this.itemLabelGeneratorList.set(series, generator);
 367:         notifyListeners(new RendererChangeEvent(this));
 368:     }
 369: 
 370:     /**
 371:      * Returns the base item label generator.
 372:      *
 373:      * @return The generator (possibly <code>null</code>).
 374:      */
 375:     public XYItemLabelGenerator getBaseItemLabelGenerator() {
 376:         return this.baseItemLabelGenerator;
 377:     }
 378: 
 379:     /**
 380:      * Sets the base item label generator and sends a
 381:      * {@link RendererChangeEvent} to all registered listeners.
 382:      *
 383:      * @param generator  the generator (<code>null</code> permitted).
 384:      */
 385:     public void setBaseItemLabelGenerator(XYItemLabelGenerator generator) {
 386:         this.baseItemLabelGenerator = generator;
 387:         notifyListeners(new RendererChangeEvent(this));
 388:     }
 389: 
 390:     // TOOL TIP GENERATOR
 391: 
 392:     /**
 393:      * Returns the tool tip generator for a data item.  If, for some reason, 
 394:      * you want a different generator for individual items, you can override 
 395:      * this method.
 396:      *
 397:      * @param series  the series index (zero based).
 398:      * @param item  the item index (zero based).
 399:      *
 400:      * @return The generator (possibly <code>null</code>).
 401:      */
 402:     public XYToolTipGenerator getToolTipGenerator(int series, int item) {
 403:         // return the generator for ALL series, if there is one...
 404:         if (this.toolTipGenerator != null) {
 405:             return this.toolTipGenerator;
 406:         }
 407: 
 408:         // otherwise look up the generator table
 409:         XYToolTipGenerator generator
 410:                 = (XYToolTipGenerator) this.toolTipGeneratorList.get(series);
 411:         if (generator == null) {
 412:             generator = this.baseToolTipGenerator;
 413:         }
 414:         return generator;
 415:     }
 416: 
 417:     /**
 418:      * Returns the override tool tip generator.
 419:      * 
 420:      * @return The tool tip generator (possible <code>null</code>).
 421:      * 
 422:      * @since 1.0.5
 423:      * 
 424:      * @see #setToolTipGenerator(XYToolTipGenerator)
 425:      */
 426:     public XYToolTipGenerator getToolTipGenerator() {
 427:         return this.toolTipGenerator;
 428:     }
 429:     
 430:     /**
 431:      * Sets the tool tip generator for ALL series and sends a
 432:      * {@link RendererChangeEvent} to all registered listeners.
 433:      *
 434:      * @param generator  the generator (<code>null</code> permitted).
 435:      * 
 436:      * @see #getToolTipGenerator()
 437:      */
 438:     public void setToolTipGenerator(XYToolTipGenerator generator) {
 439:         this.toolTipGenerator = generator;
 440:         notifyListeners(new RendererChangeEvent(this));
 441:     }
 442: 
 443:     /**
 444:      * Returns the tool tip generator for a series.
 445:      *
 446:      * @param series  the series index (zero based).
 447:      *
 448:      * @return The generator (possibly <code>null</code>).
 449:      */
 450:     public XYToolTipGenerator getSeriesToolTipGenerator(int series) {
 451:         return (XYToolTipGenerator) this.toolTipGeneratorList.get(series);
 452:     }
 453: 
 454:     /**
 455:      * Sets the tool tip generator for a series and sends a
 456:      * {@link RendererChangeEvent} to all registered listeners.
 457:      *
 458:      * @param series  the series index (zero based).
 459:      * @param generator  the generator (<code>null</code> permitted).
 460:      */
 461:     public void setSeriesToolTipGenerator(int series,
 462:                                           XYToolTipGenerator generator) {
 463:         this.toolTipGeneratorList.set(series, generator);
 464:         notifyListeners(new RendererChangeEvent(this));
 465:     }
 466: 
 467:     /**
 468:      * Returns the base tool tip generator.
 469:      *
 470:      * @return The generator (possibly <code>null</code>).
 471:      * 
 472:      * @see #setBaseToolTipGenerator(XYToolTipGenerator)
 473:      */
 474:     public XYToolTipGenerator getBaseToolTipGenerator() {
 475:         return this.baseToolTipGenerator;
 476:     }
 477: 
 478:     /**
 479:      * Sets the base tool tip generator and sends a {@link RendererChangeEvent}
 480:      * to all registered listeners.
 481:      *
 482:      * @param generator  the generator (<code>null</code> permitted).
 483:      * 
 484:      * @see #getBaseToolTipGenerator()
 485:      */
 486:     public void setBaseToolTipGenerator(XYToolTipGenerator generator) {
 487:         this.baseToolTipGenerator = generator;
 488:         notifyListeners(new RendererChangeEvent(this));
 489:     }
 490: 
 491:     // URL GENERATOR
 492: 
 493:     /**
 494:      * Returns the URL generator for HTML image maps.
 495:      *
 496:      * @return The URL generator (possibly <code>null</code>).
 497:      */
 498:     public XYURLGenerator getURLGenerator() {
 499:         return this.urlGenerator;
 500:     }
 501: 
 502:     /**
 503:      * Sets the URL generator for HTML image maps.
 504:      *
 505:      * @param urlGenerator  the URL generator (<code>null</code> permitted).
 506:      */
 507:     public void setURLGenerator(XYURLGenerator urlGenerator) {
 508:         this.urlGenerator = urlGenerator;
 509:         notifyListeners(new RendererChangeEvent(this));
 510:     }
 511: 
 512:     /**
 513:      * Adds an annotation and sends a {@link RendererChangeEvent} to all
 514:      * registered listeners.  The annotation is added to the foreground
 515:      * layer.
 516:      *
 517:      * @param annotation  the annotation (<code>null</code> not permitted).
 518:      */
 519:     public void addAnnotation(XYAnnotation annotation) {
 520:         // defer argument checking
 521:         addAnnotation(annotation, Layer.FOREGROUND);
 522:     }
 523: 
 524:     /**
 525:      * Adds an annotation to the specified layer.
 526:      *
 527:      * @param annotation  the annotation (<code>null</code> not permitted).
 528:      * @param layer  the layer (<code>null</code> not permitted).
 529:      */
 530:     public void addAnnotation(XYAnnotation annotation, Layer layer) {
 531:         if (annotation == null) {
 532:             throw new IllegalArgumentException("Null 'annotation' argument.");
 533:         }
 534:         if (layer.equals(Layer.FOREGROUND)) {
 535:             this.foregroundAnnotations.add(annotation);
 536:             notifyListeners(new RendererChangeEvent(this));
 537:         }
 538:         else if (layer.equals(Layer.BACKGROUND)) {
 539:             this.backgroundAnnotations.add(annotation);
 540:             notifyListeners(new RendererChangeEvent(this));
 541:         }
 542:         else {
 543:             // should never get here
 544:             throw new RuntimeException("Unknown layer.");
 545:         }
 546:     }
 547:     /**
 548:      * Removes the specified annotation and sends a {@link RendererChangeEvent}
 549:      * to all registered listeners.
 550:      *
 551:      * @param annotation  the annotation to remove (<code>null</code> not
 552:      *                    permitted).
 553:      *
 554:      * @return A boolean to indicate whether or not the annotation was
 555:      *         successfully removed.
 556:      */
 557:     public boolean removeAnnotation(XYAnnotation annotation) {
 558:         boolean removed = this.foregroundAnnotations.remove(annotation);
 559:         removed = removed & this.backgroundAnnotations.remove(annotation);
 560:         notifyListeners(new RendererChangeEvent(this));
 561:         return removed;
 562:     }
 563: 
 564:     /**
 565:      * Removes all annotations and sends a {@link RendererChangeEvent}
 566:      * to all registered listeners.
 567:      */
 568:     public void removeAnnotations() {
 569:         this.foregroundAnnotations.clear();
 570:         this.backgroundAnnotations.clear();
 571:         notifyListeners(new RendererChangeEvent(this));
 572:     }
 573: 
 574:     /**
 575:      * Returns the radius of the circle used for the default entity area
 576:      * when no area is specified.
 577:      *
 578:      * @return A radius.
 579:      */
 580:     public int getDefaultEntityRadius() {
 581:         return this.defaultEntityRadius;
 582:     }
 583: 
 584:     /**
 585:      * Sets the radius of the circle used for the default entity area
 586:      * when no area is specified.
 587:      *
 588:      * @param radius  the radius.
 589:      */
 590:     public void setDefaultEntityRadius(int radius) {
 591:         this.defaultEntityRadius = radius;
 592:     }
 593: 
 594:     /**
 595:      * Returns the legend item label generator.
 596:      *
 597:      * @return The label generator (never <code>null</code>).
 598:      *
 599:      * @see #setLegendItemLabelGenerator(XYSeriesLabelGenerator)
 600:      */
 601:     public XYSeriesLabelGenerator getLegendItemLabelGenerator() {
 602:         return this.legendItemLabelGenerator;
 603:     }
 604: 
 605:     /**
 606:      * Sets the legend item label generator and sends a
 607:      * {@link RendererChangeEvent} to all registered listeners.
 608:      *
 609:      * @param generator  the generator (<code>null</code> not permitted).
 610:      *
 611:      * @see #getLegendItemLabelGenerator()
 612:      */
 613:     public void setLegendItemLabelGenerator(XYSeriesLabelGenerator generator) {
 614:         if (generator == null) {
 615:             throw new IllegalArgumentException("Null 'generator' argument.");
 616:         }
 617:         this.legendItemLabelGenerator = generator;
 618:         notifyListeners(new RendererChangeEvent(this));
 619:     }
 620: 
 621:     /**
 622:      * Returns the legend item tool tip generator.
 623:      *
 624:      * @return The tool tip generator (possibly <code>null</code>).
 625:      *
 626:      * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator)
 627:      */
 628:     public XYSeriesLabelGenerator getLegendItemToolTipGenerator() {
 629:         return this.legendItemToolTipGenerator;
 630:     }
 631: 
 632:     /**
 633:      * Sets the legend item tool tip generator and sends a
 634:      * {@link RendererChangeEvent} to all registered listeners.
 635:      *
 636:      * @param generator  the generator (<code>null</code> permitted).
 637:      *
 638:      * @see #getLegendItemToolTipGenerator()
 639:      */
 640:     public void setLegendItemToolTipGenerator(
 641:             XYSeriesLabelGenerator generator) {
 642:         this.legendItemToolTipGenerator = generator;
 643:         notifyListeners(new RendererChangeEvent(this));
 644:     }
 645: 
 646:     /**
 647:      * Returns the legend item URL generator.
 648:      *
 649:      * @return The URL generator (possibly <code>null</code>).
 650:      *
 651:      * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator)
 652:      */
 653:     public XYSeriesLabelGenerator getLegendItemURLGenerator() {
 654:         return this.legendItemURLGenerator;
 655:     }
 656: 
 657:     /**
 658:      * Sets the legend item URL generator and sends a
 659:      * {@link RendererChangeEvent} to all registered listeners.
 660:      *
 661:      * @param generator  the generator (<code>null</code> permitted).
 662:      *
 663:      * @see #getLegendItemURLGenerator()
 664:      */
 665:     public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) {
 666:         this.legendItemURLGenerator = generator;
 667:         notifyListeners(new RendererChangeEvent(this));
 668:     }
 669: 
 670:     /**
 671:      * Returns the lower and upper bounds (range) of the x-values in the
 672:      * specified dataset.
 673:      *
 674:      * @param dataset  the dataset (<code>null</code> permitted).
 675:      *
 676:      * @return The range (<code>null</code> if the dataset is <code>null</code>
 677:      *         or empty).
 678:      */
 679:     public Range findDomainBounds(XYDataset dataset) {
 680:         if (dataset != null) {
 681:             return DatasetUtilities.findDomainBounds(dataset, false);
 682:         }
 683:         else {
 684:             return null;
 685:         }
 686:     }
 687: 
 688:     /**
 689:      * Returns the range of values the renderer requires to display all the
 690:      * items from the specified dataset.
 691:      *
 692:      * @param dataset  the dataset (<code>null</code> permitted).
 693:      *
 694:      * @return The range (<code>null</code> if the dataset is <code>null</code>
 695:      *         or empty).
 696:      */
 697:     public Range findRangeBounds(XYDataset dataset) {
 698:         if (dataset != null) {
 699:             return DatasetUtilities.findRangeBounds(dataset, false);
 700:         }
 701:         else {
 702:             return null;
 703:         }
 704:     }
 705: 
 706:     /**
 707:      * Returns a (possibly empty) collection of legend items for the series
 708:      * that this renderer is responsible for drawing.
 709:      *
 710:      * @return The legend item collection (never <code>null</code>).
 711:      */
 712:     public LegendItemCollection getLegendItems() {
 713:         if (this.plot == null) {
 714:             return new LegendItemCollection();
 715:         }
 716:         LegendItemCollection result = new LegendItemCollection();
 717:         int index = this.plot.getIndexOf(this);
 718:         XYDataset dataset = this.plot.getDataset(index);
 719:         if (dataset != null) {
 720:             int seriesCount = dataset.getSeriesCount();
 721:             for (int i = 0; i < seriesCount; i++) {
 722:                 if (isSeriesVisibleInLegend(i)) {
 723:                     LegendItem item = getLegendItem(index, i);
 724:                     if (item != null) {
 725:                         result.add(item);
 726:                     }
 727:                 }
 728:             }
 729: 
 730:         }
 731:         return result;
 732:     }
 733: 
 734:     /**
 735:      * Returns a default legend item for the specified series.  Subclasses
 736:      * should override this method to generate customised items.
 737:      *
 738:      * @param datasetIndex  the dataset index (zero-based).
 739:      * @param series  the series index (zero-based).
 740:      *
 741:      * @return A legend item for the series.
 742:      */
 743:     public LegendItem getLegendItem(int datasetIndex, int series) {
 744:         LegendItem result = null;
 745:         XYPlot xyplot = getPlot();
 746:         if (xyplot != null) {
 747:             XYDataset dataset = xyplot.getDataset(datasetIndex);
 748:             if (dataset != null) {
 749:                 String label = this.legendItemLabelGenerator.generateLabel(
 750:                         dataset, series);
 751:                 String description = label;
 752:                 String toolTipText = null;
 753:                 if (getLegendItemToolTipGenerator() != null) {
 754:                     toolTipText = getLegendItemToolTipGenerator().generateLabel(
 755:                             dataset, series);
 756:                 }
 757:                 String urlText = null;
 758:                 if (getLegendItemURLGenerator() != null) {
 759:                     urlText = getLegendItemURLGenerator().generateLabel(
 760:                             dataset, series);
 761:                 }
 762:                 Shape shape = getSeriesShape(series);
 763:                 Paint paint = getSeriesPaint(series);
 764:                 Paint outlinePaint = getSeriesOutlinePaint(series);
 765:                 Stroke outlineStroke = getSeriesOutlineStroke(series);
 766:                 result = new LegendItem(label, description, toolTipText,
 767:                         urlText, shape, paint, outlineStroke, outlinePaint);
 768:                 result.setSeriesIndex(series);
 769:                 result.setDatasetIndex(datasetIndex);
 770:             }
 771:         }
 772:         return result;
 773:     }
 774: 
 775:     /**
 776:      * Fills a band between two values on the axis.  This can be used to color
 777:      * bands between the grid lines.
 778:      *
 779:      * @param g2  the graphics device.
 780:      * @param plot  the plot.
 781:      * @param axis  the domain axis.
 782:      * @param dataArea  the data area.
 783:      * @param start  the start value.
 784:      * @param end  the end value.
 785:      */
 786:     public void fillDomainGridBand(Graphics2D g2,
 787:                                    XYPlot plot,
 788:                                    ValueAxis axis,
 789:                                    Rectangle2D dataArea,
 790:                                    double start, double end) {
 791: 
 792:         double x1 = axis.valueToJava2D(start, dataArea,
 793:                 plot.getDomainAxisEdge());
 794:         double x2 = axis.valueToJava2D(end, dataArea,
 795:                 plot.getDomainAxisEdge());
 796:         // TODO: need to change the next line to take account of plot
 797:         //       orientation...
 798:         Rectangle2D band = new Rectangle2D.Double(x1, dataArea.getMinY(),
 799:                 x2 - x1, dataArea.getMaxY() - dataArea.getMinY());
 800:         Paint paint = plot.getDomainTickBandPaint();
 801: 
 802:         if (paint != null) {
 803:             g2.setPaint(paint);
 804:             g2.fill(band);
 805:         }
 806: 
 807:     }
 808: 
 809:     /**
 810:      * Fills a band between two values on the range axis.  This can be used to
 811:      * color bands between the grid lines.
 812:      *
 813:      * @param g2  the graphics device.
 814:      * @param plot  the plot.
 815:      * @param axis  the range axis.
 816:      * @param dataArea  the data area.
 817:      * @param start  the start value.
 818:      * @param end  the end value.
 819:      */
 820:     public void fillRangeGridBand(Graphics2D g2,
 821:                                   XYPlot plot,
 822:                                   ValueAxis axis,
 823:                                   Rectangle2D dataArea,
 824:                                   double start, double end) {
 825: 
 826:         double y1 = axis.valueToJava2D(start, dataArea,
 827:                 plot.getRangeAxisEdge());
 828:         double y2 = axis.valueToJava2D(end, dataArea, plot.getRangeAxisEdge());
 829:         // TODO: need to change the next line to take account of the plot
 830:         //       orientation
 831:         Rectangle2D band = new Rectangle2D.Double(dataArea.getMinX(), y2,
 832:                 dataArea.getWidth(), y1 - y2);
 833:         Paint paint = plot.getRangeTickBandPaint();
 834: 
 835:         if (paint != null) {
 836:             g2.setPaint(paint);
 837:             g2.fill(band);
 838:         }
 839: 
 840:     }
 841: 
 842:     /**
 843:      * Draws a grid line against the range axis.
 844:      *
 845:      * @param g2  the graphics device.
 846:      * @param plot  the plot.
 847:      * @param axis  the value axis.
 848:      * @param dataArea  the area for plotting data (not yet adjusted for any
 849:      *                  3D effect).
 850:      * @param value  the value at which the grid line should be drawn.
 851:      */
 852:     public void drawDomainGridLine(Graphics2D g2,
 853:                                    XYPlot plot,
 854:                                    ValueAxis axis,
 855:                                    Rectangle2D dataArea,
 856:                                    double value) {
 857: 
 858:         Range range = axis.getRange();
 859:         if (!range.contains(value)) {
 860:             return;
 861:         }
 862: 
 863:         PlotOrientation orientation = plot.getOrientation();
 864:         double v = axis.valueToJava2D(value, dataArea,
 865:                 plot.getDomainAxisEdge());
 866:         Line2D line = null;
 867:         if (orientation == PlotOrientation.HORIZONTAL) {
 868:             line = new Line2D.Double(dataArea.getMinX(), v,
 869:                     dataArea.getMaxX(), v);
 870:         }
 871:         else if (orientation == PlotOrientation.VERTICAL) {
 872:             line = new Line2D.Double(v, dataArea.getMinY(), v,
 873:                     dataArea.getMaxY());
 874:         }
 875: 
 876:         Paint paint = plot.getDomainGridlinePaint();
 877:         Stroke stroke = plot.getDomainGridlineStroke();
 878:         g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
 879:         g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
 880:         g2.draw(line);
 881: 
 882:     }
 883: 
 884:     /**
 885:      * Draws a line perpendicular to the domain axis.
 886:      *
 887:      * @param g2  the graphics device.
 888:      * @param plot  the plot.
 889:      * @param axis  the value axis.
 890:      * @param dataArea  the area for plotting data (not yet adjusted for any 3D
 891:      *                  effect).
 892:      * @param value  the value at which the grid line should be drawn.
 893:      * @param paint  the paint.
 894:      * @param stroke  the stroke.
 895:      * 
 896:      * @since 1.0.5
 897:      */
 898:     public void drawDomainLine(Graphics2D g2, XYPlot plot, ValueAxis axis,
 899:             Rectangle2D dataArea, double value, Paint paint, Stroke stroke) {
 900: 
 901:         Range range = axis.getRange();
 902:         if (!range.contains(value)) {
 903:             return;
 904:         }
 905: 
 906:         PlotOrientation orientation = plot.getOrientation();
 907:         Line2D line = null;
 908:         double v = axis.valueToJava2D(value, dataArea, plot.getDomainAxisEdge());
 909:         if (orientation == PlotOrientation.HORIZONTAL) {
 910:             line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), 
 911:                     v);
 912:         }
 913:         else if (orientation == PlotOrientation.VERTICAL) {
 914:             line = new Line2D.Double(v, dataArea.getMinY(), v, 
 915:                     dataArea.getMaxY());
 916:         }
 917: 
 918:         g2.setPaint(paint);
 919:         g2.setStroke(stroke);
 920:         g2.draw(line);
 921: 
 922:     }
 923: 
 924:     /**
 925:      * Draws a line perpendicular to the range axis.
 926:      *
 927:      * @param g2  the graphics device.
 928:      * @param plot  the plot.
 929:      * @param axis  the value axis.
 930:      * @param dataArea  the area for plotting data (not yet adjusted for any 3D
 931:      *                  effect).
 932:      * @param value  the value at which the grid line should be drawn.
 933:      * @param paint  the paint.
 934:      * @param stroke  the stroke.
 935:      */
 936:     public void drawRangeLine(Graphics2D g2,
 937:                               XYPlot plot,
 938:                               ValueAxis axis,
 939:                               Rectangle2D dataArea,
 940:                               double value,
 941:                               Paint paint,
 942:                               Stroke stroke) {
 943: 
 944:         Range range = axis.getRange();
 945:         if (!range.contains(value)) {
 946:             return;
 947:         }
 948: 
 949:         PlotOrientation orientation = plot.getOrientation();
 950:         Line2D line = null;
 951:         double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
 952:         if (orientation == PlotOrientation.HORIZONTAL) {
 953:             line = new Line2D.Double(v, dataArea.getMinY(), v,
 954:                     dataArea.getMaxY());
 955:         }
 956:         else if (orientation == PlotOrientation.VERTICAL) {
 957:             line = new Line2D.Double(dataArea.getMinX(), v,
 958:                     dataArea.getMaxX(), v);
 959:         }
 960: 
 961:         g2.setPaint(paint);
 962:         g2.setStroke(stroke);
 963:         g2.draw(line);
 964: 
 965:     }
 966: 
 967:     /**
 968:      * Draws a vertical line on the chart to represent a 'range marker'.
 969:      *
 970:      * @param g2  the graphics device.
 971:      * @param plot  the plot.
 972:      * @param domainAxis  the domain axis.
 973:      * @param marker  the marker line.
 974:      * @param dataArea  the axis data area.
 975:      */
 976:     public void drawDomainMarker(Graphics2D g2,
 977:                                  XYPlot plot,
 978:                                  ValueAxis domainAxis,
 979:                                  Marker marker,
 980:                                  Rectangle2D dataArea) {
 981: 
 982:         if (marker instanceof ValueMarker) {
 983:             ValueMarker vm = (ValueMarker) marker;
 984:             double value = vm.getValue();
 985:             Range range = domainAxis.getRange();
 986:             if (!range.contains(value)) {
 987:                 return;
 988:             }
 989: 
 990:             double v = domainAxis.valueToJava2D(value, dataArea,
 991:                     plot.getDomainAxisEdge());
 992: 
 993:             PlotOrientation orientation = plot.getOrientation();
 994:             Line2D line = null;
 995:             if (orientation == PlotOrientation.HORIZONTAL) {
 996:                 line = new Line2D.Double(dataArea.getMinX(), v,
 997:                         dataArea.getMaxX(), v);
 998:             }
 999:             else if (orientation == PlotOrientation.VERTICAL) {
1000:                 line = new Line2D.Double(v, dataArea.getMinY(), v,
1001:                         dataArea.getMaxY());
1002:             }
1003: 
1004:             final Composite originalComposite = g2.getComposite();
1005:             g2.setComposite(AlphaComposite.getInstance(
1006:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1007:             g2.setPaint(marker.getPaint());
1008:             g2.setStroke(marker.getStroke());
1009:             g2.draw(line);
1010: 
1011:             String label = marker.getLabel();
1012:             RectangleAnchor anchor = marker.getLabelAnchor();
1013:             if (label != null) {
1014:                 Font labelFont = marker.getLabelFont();
1015:                 g2.setFont(labelFont);
1016:                 g2.setPaint(marker.getLabelPaint());
1017:                 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
1018:                         g2, orientation, dataArea, line.getBounds2D(),
1019:                         marker.getLabelOffset(),
1020:                         LengthAdjustmentType.EXPAND, anchor);
1021:                 TextUtilities.drawAlignedString(label, g2,
1022:                         (float) coordinates.getX(), (float) coordinates.getY(),
1023:                         marker.getLabelTextAnchor());
1024:             }
1025:             g2.setComposite(originalComposite);
1026:         }
1027:         else if (marker instanceof IntervalMarker) {
1028:             IntervalMarker im = (IntervalMarker) marker;
1029:             double start = im.getStartValue();
1030:             double end = im.getEndValue();
1031:             Range range = domainAxis.getRange();
1032:             if (!(range.intersects(start, end))) {
1033:                 return;
1034:             }
1035: 
1036:             double start2d = domainAxis.valueToJava2D(start, dataArea,
1037:                     plot.getDomainAxisEdge());
1038:             double end2d = domainAxis.valueToJava2D(end, dataArea,
1039:                     plot.getDomainAxisEdge());
1040:             double low = Math.min(start2d, end2d);
1041:             double high = Math.max(start2d, end2d);
1042: 
1043:             PlotOrientation orientation = plot.getOrientation();
1044:             Rectangle2D rect = null;
1045:             if (orientation == PlotOrientation.HORIZONTAL) {
1046:                 // clip top and bottom bounds to data area
1047:                 low = Math.max(low, dataArea.getMinY());
1048:                 high = Math.min(high, dataArea.getMaxY());
1049:                 rect = new Rectangle2D.Double(dataArea.getMinX(),
1050:                         low, dataArea.getWidth(),
1051:                         high - low);
1052:             }
1053:             else if (orientation == PlotOrientation.VERTICAL) {
1054:                 // clip left and right bounds to data area
1055:                 low = Math.max(low, dataArea.getMinX());
1056:                 high = Math.min(high, dataArea.getMaxX());
1057:                 rect = new Rectangle2D.Double(low,
1058:                         dataArea.getMinY(), high - low,
1059:                         dataArea.getHeight());
1060:             }
1061: 
1062:             final Composite originalComposite = g2.getComposite();
1063:             g2.setComposite(AlphaComposite.getInstance(
1064:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1065:             Paint p = marker.getPaint();
1066:             if (p instanceof GradientPaint) {
1067:                 GradientPaint gp = (GradientPaint) p;
1068:                 GradientPaintTransformer t = im.getGradientPaintTransformer();
1069:                 if (t != null) {
1070:                     gp = t.transform(gp, rect);
1071:                 }
1072:                 g2.setPaint(gp);
1073:             }
1074:             else {
1075:                 g2.setPaint(p);
1076:             }
1077:             g2.fill(rect);
1078: 
1079:             // now draw the outlines, if visible...
1080:             if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1081:                 if (orientation == PlotOrientation.VERTICAL) {
1082:                     Line2D line = new Line2D.Double();
1083:                     double y0 = dataArea.getMinY();
1084:                     double y1 = dataArea.getMaxY();
1085:                     g2.setPaint(im.getOutlinePaint());
1086:                     g2.setStroke(im.getOutlineStroke());
1087:                     if (range.contains(start)) {
1088:                         line.setLine(start2d, y0, start2d, y1);
1089:                         g2.draw(line);
1090:                     }
1091:                     if (range.contains(end)) {
1092:                         line.setLine(end2d, y0, end2d, y1);
1093:                         g2.draw(line);
1094:                     }
1095:                 }
1096:                 else { // PlotOrientation.HORIZONTAL
1097:                     Line2D line = new Line2D.Double();
1098:                     double x0 = dataArea.getMinX();
1099:                     double x1 = dataArea.getMaxX();
1100:                     g2.setPaint(im.getOutlinePaint());
1101:                     g2.setStroke(im.getOutlineStroke());
1102:                     if (range.contains(start)) {
1103:                         line.setLine(x0, start2d, x1, start2d);
1104:                         g2.draw(line);
1105:                     }
1106:                     if (range.contains(end)) {
1107:                         line.setLine(x0, end2d, x1, end2d);
1108:                         g2.draw(line);
1109:                     }
1110:                 }
1111:             }
1112: 
1113:             String label = marker.getLabel();
1114:             RectangleAnchor anchor = marker.getLabelAnchor();
1115:             if (label != null) {
1116:                 Font labelFont = marker.getLabelFont();
1117:                 g2.setFont(labelFont);
1118:                 g2.setPaint(marker.getLabelPaint());
1119:                 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
1120:                         g2, orientation, dataArea, rect,
1121:                         marker.getLabelOffset(), marker.getLabelOffsetType(),
1122:                         anchor);
1123:                 TextUtilities.drawAlignedString(label, g2,
1124:                         (float) coordinates.getX(), (float) coordinates.getY(),
1125:                         marker.getLabelTextAnchor());
1126:             }
1127:             g2.setComposite(originalComposite);
1128: 
1129:         }
1130: 
1131:     }
1132: 
1133:     /**
1134:      * Calculates the (x, y) coordinates for drawing a marker label.
1135:      *
1136:      * @param g2  the graphics device.
1137:      * @param orientation  the plot orientation.
1138:      * @param dataArea  the data area.
1139:      * @param markerArea  the rectangle surrounding the marker area.
1140:      * @param markerOffset  the marker label offset.
1141:      * @param labelOffsetType  the label offset type.
1142:      * @param anchor  the label anchor.
1143:      *
1144:      * @return The coordinates for drawing the marker label.
1145:      */
1146:     protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2,
1147:             PlotOrientation orientation,
1148:             Rectangle2D dataArea,
1149:             Rectangle2D markerArea,
1150:             RectangleInsets markerOffset,
1151:             LengthAdjustmentType labelOffsetType,
1152:             RectangleAnchor anchor) {
1153: 
1154:         Rectangle2D anchorRect = null;
1155:         if (orientation == PlotOrientation.HORIZONTAL) {
1156:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1157:                     LengthAdjustmentType.CONTRACT, labelOffsetType);
1158:         }
1159:         else if (orientation == PlotOrientation.VERTICAL) {
1160:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1161:                     labelOffsetType, LengthAdjustmentType.CONTRACT);
1162:         }
1163:         return RectangleAnchor.coordinates(anchorRect, anchor);
1164: 
1165:     }
1166: 
1167:     /**
1168:      * Draws a horizontal line across the chart to represent a 'range marker'.
1169:      *
1170:      * @param g2  the graphics device.
1171:      * @param plot  the plot.
1172:      * @param rangeAxis  the range axis.
1173:      * @param marker  the marker line.
1174:      * @param dataArea  the axis data area.
1175:      */
1176:     public void drawRangeMarker(Graphics2D g2,
1177:                                 XYPlot plot,
1178:                                 ValueAxis rangeAxis,
1179:                                 Marker marker,
1180:                                 Rectangle2D dataArea) {
1181: 
1182:         if (marker instanceof ValueMarker) {
1183:             ValueMarker vm = (ValueMarker) marker;
1184:             double value = vm.getValue();
1185:             Range range = rangeAxis.getRange();
1186:             if (!range.contains(value)) {
1187:                 return;
1188:             }
1189: 
1190:             double v = rangeAxis.valueToJava2D(value, dataArea,
1191:                     plot.getRangeAxisEdge());
1192:             PlotOrientation orientation = plot.getOrientation();
1193:             Line2D line = null;
1194:             if (orientation == PlotOrientation.HORIZONTAL) {
1195:                 line = new Line2D.Double(v, dataArea.getMinY(), v,
1196:                         dataArea.getMaxY());
1197:             }
1198:             else if (orientation == PlotOrientation.VERTICAL) {
1199:                 line = new Line2D.Double(dataArea.getMinX(), v,
1200:                         dataArea.getMaxX(), v);
1201:             }
1202: 
1203:             final Composite originalComposite = g2.getComposite();
1204:             g2.setComposite(AlphaComposite.getInstance(
1205:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1206:             g2.setPaint(marker.getPaint());
1207:             g2.setStroke(marker.getStroke());
1208:             g2.draw(line);
1209: 
1210:             String label = marker.getLabel();
1211:             RectangleAnchor anchor = marker.getLabelAnchor();
1212:             if (label != null) {
1213:                 Font labelFont = marker.getLabelFont();
1214:                 g2.setFont(labelFont);
1215:                 g2.setPaint(marker.getLabelPaint());
1216:                 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1217:                         g2, orientation, dataArea, line.getBounds2D(),
1218:                         marker.getLabelOffset(),
1219:                         LengthAdjustmentType.EXPAND, anchor);
1220:                 TextUtilities.drawAlignedString(label, g2,
1221:                         (float) coordinates.getX(), (float) coordinates.getY(),
1222:                         marker.getLabelTextAnchor());
1223:             }
1224:             g2.setComposite(originalComposite);
1225:         }
1226:         else if (marker instanceof IntervalMarker) {
1227:             IntervalMarker im = (IntervalMarker) marker;
1228:             double start = im.getStartValue();
1229:             double end = im.getEndValue();
1230:             Range range = rangeAxis.getRange();
1231:             if (!(range.intersects(start, end))) {
1232:                 return;
1233:             }
1234: 
1235:             double start2d = rangeAxis.valueToJava2D(start, dataArea,
1236:                     plot.getRangeAxisEdge());
1237:             double end2d = rangeAxis.valueToJava2D(end, dataArea,
1238:                     plot.getRangeAxisEdge());
1239:             double low = Math.min(start2d, end2d);
1240:             double high = Math.max(start2d, end2d);
1241: 
1242:             PlotOrientation orientation = plot.getOrientation();
1243:             Rectangle2D rect = null;
1244:             if (orientation == PlotOrientation.HORIZONTAL) {
1245:                 // clip left and right bounds to data area
1246:                 low = Math.max(low, dataArea.getMinX());
1247:                 high = Math.min(high, dataArea.getMaxX());
1248:                 rect = new Rectangle2D.Double(low,
1249:                         dataArea.getMinY(), high - low,
1250:                         dataArea.getHeight());
1251:             }
1252:             else if (orientation == PlotOrientation.VERTICAL) {
1253:                 // clip top and bottom bounds to data area
1254:                 low = Math.max(low, dataArea.getMinY());
1255:                 high = Math.min(high, dataArea.getMaxY());
1256:                 rect = new Rectangle2D.Double(dataArea.getMinX(),
1257:                         low, dataArea.getWidth(),
1258:                         high - low);
1259:             }
1260: 
1261:             final Composite originalComposite = g2.getComposite();
1262:             g2.setComposite(AlphaComposite.getInstance(
1263:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1264:             Paint p = marker.getPaint();
1265:             if (p instanceof GradientPaint) {
1266:                 GradientPaint gp = (GradientPaint) p;
1267:                 GradientPaintTransformer t = im.getGradientPaintTransformer();
1268:                 if (t != null) {
1269:                     gp = t.transform(gp, rect);
1270:                 }
1271:                 g2.setPaint(gp);
1272:             }
1273:             else {
1274:                 g2.setPaint(p);
1275:             }
1276:             g2.fill(rect);
1277: 
1278:             // now draw the outlines, if visible...
1279:             if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1280:                 if (orientation == PlotOrientation.VERTICAL) {
1281:                     Line2D line = new Line2D.Double();
1282:                     double x0 = dataArea.getMinX();
1283:                     double x1 = dataArea.getMaxX();
1284:                     g2.setPaint(im.getOutlinePaint());
1285:                     g2.setStroke(im.getOutlineStroke());
1286:                     if (range.contains(start)) {
1287:                         line.setLine(x0, start2d, x1, start2d);
1288:                         g2.draw(line);
1289:                     }
1290:                     if (range.contains(end)) {
1291:                         line.setLine(x0, end2d, x1, end2d);
1292:                         g2.draw(line);
1293:                     }
1294:                 }
1295:                 else { // PlotOrientation.HORIZONTAL
1296:                     Line2D line = new Line2D.Double();
1297:                     double y0 = dataArea.getMinY();
1298:                     double y1 = dataArea.getMaxY();
1299:                     g2.setPaint(im.getOutlinePaint());
1300:                     g2.setStroke(im.getOutlineStroke());
1301:                     if (range.contains(start)) {
1302:                         line.setLine(start2d, y0, start2d, y1);
1303:                         g2.draw(line);
1304:                     }
1305:                     if (range.contains(end)) {
1306:                         line.setLine(end2d, y0, end2d, y1);
1307:                         g2.draw(line);
1308:                     }
1309:                 }
1310:             }
1311: 
1312:             String label = marker.getLabel();
1313:             RectangleAnchor anchor = marker.getLabelAnchor();
1314:             if (label != null) {
1315:                 Font labelFont = marker.getLabelFont();
1316:                 g2.setFont(labelFont);
1317:                 g2.setPaint(marker.getLabelPaint());
1318:                 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1319:                         g2, orientation, dataArea, rect,
1320:                         marker.getLabelOffset(), marker.getLabelOffsetType(),
1321:                         anchor);
1322:                 TextUtilities.drawAlignedString(label, g2,
1323:                         (float) coordinates.getX(), (float) coordinates.getY(),
1324:                         marker.getLabelTextAnchor());
1325:             }
1326:             g2.setComposite(originalComposite);
1327:         }
1328:     }
1329: 
1330:     /**
1331:      * Calculates the (x, y) coordinates for drawing a marker label.
1332:      *
1333:      * @param g2  the graphics device.
1334:      * @param orientation  the plot orientation.
1335:      * @param dataArea  the data area.
1336:      * @param markerArea  the marker area.
1337:      * @param markerOffset  the marker offset.
1338:      * @param anchor  the label anchor.
1339:      *
1340:      * @return The coordinates for drawing the marker label.
1341:      */
1342:     private Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2,
1343:                                       PlotOrientation orientation,
1344:                                       Rectangle2D dataArea,
1345:                                       Rectangle2D markerArea,
1346:                                       RectangleInsets markerOffset,
1347:                                       LengthAdjustmentType labelOffsetForRange,
1348:                                       RectangleAnchor anchor) {
1349: 
1350:         Rectangle2D anchorRect = null;
1351:         if (orientation == PlotOrientation.HORIZONTAL) {
1352:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1353:                     labelOffsetForRange, LengthAdjustmentType.CONTRACT);
1354:         }
1355:         else if (orientation == PlotOrientation.VERTICAL) {
1356:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1357:                     LengthAdjustmentType.CONTRACT, labelOffsetForRange);
1358:         }
1359:         return RectangleAnchor.coordinates(anchorRect, anchor);
1360: 
1361:     }
1362: 
1363:     /**
1364:      * Returns a clone of the renderer.
1365:      *
1366:      * @return A clone.
1367:      *
1368:      * @throws CloneNotSupportedException if the renderer does not support
1369:      *         cloning.
1370:      */
1371:     protected Object clone() throws CloneNotSupportedException {
1372:         AbstractXYItemRenderer clone = (AbstractXYItemRenderer) super.clone();
1373:         // 'plot' : just retain reference, not a deep copy
1374: 
1375:         if (this.itemLabelGenerator != null
1376:                 && this.itemLabelGenerator instanceof PublicCloneable) {
1377:             PublicCloneable pc = (PublicCloneable) this.itemLabelGenerator;
1378:             clone.itemLabelGenerator = (XYItemLabelGenerator) pc.clone();
1379:         }
1380:         clone.itemLabelGeneratorList
1381:                 = (ObjectList) this.itemLabelGeneratorList.clone();
1382:         if (this.baseItemLabelGenerator != null
1383:                 && this.baseItemLabelGenerator instanceof PublicCloneable) {
1384:             PublicCloneable pc = (PublicCloneable) this.baseItemLabelGenerator;
1385:             clone.baseItemLabelGenerator = (XYItemLabelGenerator) pc.clone();
1386:         }
1387: 
1388:         if (this.toolTipGenerator != null
1389:                 && this.toolTipGenerator instanceof PublicCloneable) {
1390:             PublicCloneable pc = (PublicCloneable) this.toolTipGenerator;
1391:             clone.toolTipGenerator = (XYToolTipGenerator) pc.clone();
1392:         }
1393:         clone.toolTipGeneratorList
1394:                 = (ObjectList) this.toolTipGeneratorList.clone();
1395:         if (this.baseToolTipGenerator != null
1396:                 && this.baseToolTipGenerator instanceof PublicCloneable) {
1397:             PublicCloneable pc = (PublicCloneable) this.baseToolTipGenerator;
1398:             clone.baseToolTipGenerator = (XYToolTipGenerator) pc.clone();
1399:         }
1400: 
1401:         if (clone.legendItemLabelGenerator instanceof PublicCloneable) {
1402:             clone.legendItemLabelGenerator = (XYSeriesLabelGenerator)
1403:                     ObjectUtilities.clone(this.legendItemLabelGenerator);
1404:         }
1405:         if (clone.legendItemToolTipGenerator instanceof PublicCloneable) {
1406:             clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator)
1407:                     ObjectUtilities.clone(this.legendItemToolTipGenerator);
1408:         }
1409:         if (clone.legendItemURLGenerator instanceof PublicCloneable) {
1410:             clone.legendItemURLGenerator = (XYSeriesLabelGenerator)
1411:                     ObjectUtilities.clone(this.legendItemURLGenerator);
1412:         }
1413: 
1414:         clone.foregroundAnnotations = (List) ObjectUtilities.deepClone(
1415:                 this.foregroundAnnotations);
1416:         clone.backgroundAnnotations = (List) ObjectUtilities.deepClone(
1417:                 this.backgroundAnnotations);
1418: 
1419:         if (clone.legendItemLabelGenerator instanceof PublicCloneable) {
1420:             clone.legendItemLabelGenerator = (XYSeriesLabelGenerator)
1421:                     ObjectUtilities.clone(this.legendItemLabelGenerator);
1422:         }
1423:         if (clone.legendItemToolTipGenerator instanceof PublicCloneable) {
1424:             clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator)
1425:                     ObjectUtilities.clone(this.legendItemToolTipGenerator);
1426:         }
1427:         if (clone.legendItemURLGenerator instanceof PublicCloneable) {
1428:             clone.legendItemURLGenerator = (XYSeriesLabelGenerator)
1429:                     ObjectUtilities.clone(this.legendItemURLGenerator);
1430:         }
1431: 
1432:         return clone;
1433:     }
1434: 
1435:     /**
1436:      * Tests this renderer for equality with another object.
1437:      *
1438:      * @param obj  the object (<code>null</code> permitted).
1439:      *
1440:      * @return <code>true</code> or <code>false</code>.
1441:      */
1442:     public boolean equals(Object obj) {
1443:         if (obj == this) {
1444:             return true;
1445:         }
1446:         if (!(obj instanceof AbstractXYItemRenderer)) {
1447:             return false;
1448:         }
1449:         AbstractXYItemRenderer that = (AbstractXYItemRenderer) obj;
1450:         if (!ObjectUtilities.equal(this.itemLabelGenerator,
1451:                 that.itemLabelGenerator)) {
1452:             return false;
1453:         }
1454:         if (!this.itemLabelGeneratorList.equals(that.itemLabelGeneratorList)) {
1455:             return false;
1456:         }
1457:         if (!ObjectUtilities.equal(this.baseItemLabelGenerator,
1458:                 that.baseItemLabelGenerator)) {
1459:             return false;
1460:         }
1461:         if (!ObjectUtilities.equal(this.toolTipGenerator,
1462:                 that.toolTipGenerator)) {
1463:             return false;
1464:         }
1465:         if (!this.toolTipGeneratorList.equals(that.toolTipGeneratorList)) {
1466:             return false;
1467:         }
1468:         if (!ObjectUtilities.equal(this.baseToolTipGenerator,
1469:                 that.baseToolTipGenerator)) {
1470:             return false;
1471:         }
1472:         if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) {
1473:             return false;
1474:         }
1475:         if (!this.foregroundAnnotations.equals(that.foregroundAnnotations)) {
1476:             return false;
1477:         }
1478:         if (!this.backgroundAnnotations.equals(that.backgroundAnnotations)) {
1479:             return false;
1480:         }
1481:         if (this.defaultEntityRadius != that.defaultEntityRadius) {
1482:             return false;
1483:         }
1484:         if (!ObjectUtilities.equal(this.legendItemLabelGenerator,
1485:                 that.legendItemLabelGenerator)) {
1486:             return false;
1487:         }
1488:         if (!ObjectUtilities.equal(this.legendItemToolTipGenerator,
1489:                 that.legendItemToolTipGenerator)) {
1490:             return false;
1491:         }
1492:         if (!ObjectUtilities.equal(this.legendItemURLGenerator,
1493:                 that.legendItemURLGenerator)) {
1494:             return false;
1495:         }
1496:         return super.equals(obj);
1497:     }
1498: 
1499:     /**
1500:      * Returns the drawing supplier from the plot.
1501:      *
1502:      * @return The drawing supplier (possibly <code>null</code>).
1503:      */
1504:     public DrawingSupplier getDrawingSupplier() {
1505:         DrawingSupplier result = null;
1506:         XYPlot p = getPlot();
1507:         if (p != null) {
1508:             result = p.getDrawingSupplier();
1509:         }
1510:         return result;
1511:     }
1512: 
1513:     /**
1514:      * Considers the current (x, y) coordinate and updates the crosshair point
1515:      * if it meets the criteria (usually means the (x, y) coordinate is the
1516:      * closest to the anchor point so far).
1517:      *
1518:      * @param crosshairState  the crosshair state (<code>null</code> permitted,
1519:      *                        but the method does nothing in that case).
1520:      * @param x  the x-value (in data space).
1521:      * @param y  the y-value (in data space).
1522:      * @param transX  the x-value translated to Java2D space.
1523:      * @param transY  the y-value translated to Java2D space.
1524:      * @param orientation  the plot orientation (<code>null</code> not
1525:      *                     permitted).
1526:      *
1527:      * @deprecated Use {@link #updateCrosshairValues(CrosshairState, double,
1528:      *         double, int, int, double, double, PlotOrientation)} -- see bug
1529:      *         report 1086307.
1530:      */
1531:     protected void updateCrosshairValues(CrosshairState crosshairState,
1532:             double x, double y, double transX, double transY,
1533:             PlotOrientation orientation) {
1534:         updateCrosshairValues(crosshairState, x, y, 0, 0, transX, transY,
1535:                 orientation);
1536:     }
1537: 
1538:     /**
1539:      * Considers the current (x, y) coordinate and updates the crosshair point
1540:      * if it meets the criteria (usually means the (x, y) coordinate is the
1541:      * closest to the anchor point so far).
1542:      *
1543:      * @param crosshairState  the crosshair state (<code>null</code> permitted,
1544:      *                        but the method does nothing in that case).
1545:      * @param x  the x-value (in data space).
1546:      * @param y  the y-value (in data space).
1547:      * @param domainAxisIndex  the index of the domain axis for the point.
1548:      * @param rangeAxisIndex  the index of the range axis for the point.
1549:      * @param transX  the x-value translated to Java2D space.
1550:      * @param transY  the y-value translated to Java2D space.
1551:      * @param orientation  the plot orientation (<code>null</code> not
1552:      *                     permitted).
1553:      *
1554:      * @since 1.0.4
1555:      */
1556:     protected void updateCrosshairValues(CrosshairState crosshairState,
1557:             double x, double y, int domainAxisIndex, int rangeAxisIndex,
1558:             double transX, double transY, PlotOrientation orientation) {
1559: 
1560:         if (orientation == null) {
1561:             throw new IllegalArgumentException("Null 'orientation' argument.");
1562:         }
1563: 
1564:         if (crosshairState != null) {
1565:             // do we need to update the crosshair values?
1566:             if (this.plot.isDomainCrosshairLockedOnData()) {
1567:                 if (this.plot.isRangeCrosshairLockedOnData()) {
1568:                     // both axes
1569:                     crosshairState.updateCrosshairPoint(x, y, domainAxisIndex,
1570:                             rangeAxisIndex, transX, transY, orientation);
1571:                 }
1572:                 else {
1573:                     // just the domain axis...
1574:                     crosshairState.updateCrosshairX(x, domainAxisIndex);
1575:                 }
1576:             }
1577:             else {
1578:                 if (this.plot.isRangeCrosshairLockedOnData()) {
1579:                     // just the range axis...
1580:                     crosshairState.updateCrosshairY(y, rangeAxisIndex);
1581:                 }
1582:             }
1583:         }
1584: 
1585:     }
1586: 
1587:     /**
1588:      * Draws an item label.
1589:      *
1590:      * @param g2  the graphics device.
1591:      * @param orientation  the orientation.
1592:      * @param dataset  the dataset.
1593:      * @param series  the series index (zero-based).
1594:      * @param item  the item index (zero-based).
1595:      * @param x  the x coordinate (in Java2D space).
1596:      * @param y  the y coordinate (in Java2D space).
1597:      * @param negative  indicates a negative value (which affects the item
1598:      *                  label position).
1599:      */
1600:     protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation,
1601:             XYDataset dataset, int series, int item, double x, double y,
1602:             boolean negative) {
1603: 
1604:         XYItemLabelGenerator generator = getItemLabelGenerator(series, item);
1605:         if (generator != null) {
1606:             Font labelFont = getItemLabelFont(series, item);
1607:             Paint paint = getItemLabelPaint(series, item);
1608:             g2.setFont(labelFont);
1609:             g2.setPaint(paint);
1610:             String label = generator.generateLabel(dataset, series, item);
1611: 
1612:             // get the label position..
1613:             ItemLabelPosition position = null;
1614:             if (!negative) {
1615:                 position = getPositiveItemLabelPosition(series, item);
1616:             }
1617:             else {
1618:                 position = getNegativeItemLabelPosition(series, item);
1619:             }
1620: 
1621:             // work out the label anchor point...
1622:             Point2D anchorPoint = calculateLabelAnchorPoint(
1623:                     position.getItemLabelAnchor(), x, y, orientation);
1624:             TextUtilities.drawRotatedString(label, g2,
1625:                     (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1626:                     position.getTextAnchor(), position.getAngle(),
1627:                     position.getRotationAnchor());
1628:         }
1629: 
1630:     }
1631: 
1632:     /**
1633:      * Draws all the annotations for the specified layer.
1634:      *
1635:      * @param g2  the graphics device.
1636:      * @param dataArea  the data area.
1637:      * @param domainAxis  the domain axis.
1638:      * @param rangeAxis  the range axis.
1639:      * @param layer  the layer.
1640:      * @param info  the plot rendering info.
1641:      */
1642:     public void drawAnnotations(Graphics2D g2,
1643:                                 Rectangle2D dataArea,
1644:                                 ValueAxis domainAxis,
1645:                                 ValueAxis rangeAxis,
1646:                                 Layer layer,
1647:                                 PlotRenderingInfo info) {
1648: 
1649:         Iterator iterator = null;
1650:         if (layer.equals(Layer.FOREGROUND)) {
1651:             iterator = this.foregroundAnnotations.iterator();
1652:         }
1653:         else if (layer.equals(Layer.BACKGROUND)) {
1654:             iterator = this.backgroundAnnotations.iterator();
1655:         }
1656:         else {
1657:             // should not get here
1658:             throw new RuntimeException("Unknown layer.");
1659:         }
1660:         while (iterator.hasNext()) {
1661:             XYAnnotation annotation = (XYAnnotation) iterator.next();
1662:             annotation.draw(g2, this.plot, dataArea, domainAxis, rangeAxis,
1663:                     0, info);
1664:         }
1665: 
1666:     }
1667: 
1668:     /**
1669:      * Adds an entity to the collection.
1670:      *
1671:      * @param entities  the entity collection being populated.
1672:      * @param area  the entity area (if <code>null</code> a default will be
1673:      *              used).
1674:      * @param dataset  the dataset.
1675:      * @param series  the series.
1676:      * @param item  the item.
1677:      * @param entityX  the entity's center x-coordinate in user space.
1678:      * @param entityY  the entity's center y-coordinate in user space.
1679:      */
1680:     protected void addEntity(EntityCollection entities, Shape area,
1681:                              XYDataset dataset, int series, int item,
1682:                              double entityX, double entityY) {
1683:         if (!getItemCreateEntity(series, item)) {
1684:             return;
1685:         }
1686:         if (area == null) {
1687:             area = new Ellipse2D.Double(entityX - this.defaultEntityRadius,
1688:                     entityY - this.defaultEntityRadius,
1689:                     this.defaultEntityRadius * 2, this.defaultEntityRadius * 2);
1690:         }
1691:         String tip = null;
1692:         XYToolTipGenerator generator = getToolTipGenerator(series, item);
1693:         if (generator != null) {
1694:             tip = generator.generateToolTip(dataset, series, item);
1695:         }
1696:         String url = null;
1697:         if (getURLGenerator() != null) {
1698:             url = getURLGenerator().generateURL(dataset, series, item);
1699:         }
1700:         XYItemEntity entity = new XYItemEntity(area, dataset, series, item,
1701:                 tip, url);
1702:         entities.add(entity);
1703:     }
1704: 
1705: }