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