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:         fireChangeEvent();
 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:         fireChangeEvent();
 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:         fireChangeEvent();
 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:         fireChangeEvent();
 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:         fireChangeEvent();
 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:         fireChangeEvent();
 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 and sends a 
 533:      * {@link RendererChangeEvent} to all registered listeners.
 534:      *
 535:      * @param urlGenerator  the URL generator (<code>null</code> permitted).
 536:      */
 537:     public void setURLGenerator(XYURLGenerator urlGenerator) {
 538:         this.urlGenerator = urlGenerator;
 539:         fireChangeEvent();
 540:     }
 541: 
 542:     /**
 543:      * Adds an annotation and sends a {@link RendererChangeEvent} to all
 544:      * registered listeners.  The annotation is added to the foreground
 545:      * layer.
 546:      *
 547:      * @param annotation  the annotation (<code>null</code> not permitted).
 548:      */
 549:     public void addAnnotation(XYAnnotation annotation) {
 550:         // defer argument checking
 551:         addAnnotation(annotation, Layer.FOREGROUND);
 552:     }
 553: 
 554:     /**
 555:      * Adds an annotation to the specified layer and sends a 
 556:      * {@link RendererChangeEvent} to all registered listeners.
 557:      *
 558:      * @param annotation  the annotation (<code>null</code> not permitted).
 559:      * @param layer  the layer (<code>null</code> not permitted).
 560:      */
 561:     public void addAnnotation(XYAnnotation annotation, Layer layer) {
 562:         if (annotation == null) {
 563:             throw new IllegalArgumentException("Null 'annotation' argument.");
 564:         }
 565:         if (layer.equals(Layer.FOREGROUND)) {
 566:             this.foregroundAnnotations.add(annotation);
 567:             fireChangeEvent();
 568:         }
 569:         else if (layer.equals(Layer.BACKGROUND)) {
 570:             this.backgroundAnnotations.add(annotation);
 571:             fireChangeEvent();
 572:         }
 573:         else {
 574:             // should never get here
 575:             throw new RuntimeException("Unknown layer.");
 576:         }
 577:     }
 578:     /**
 579:      * Removes the specified annotation and sends a {@link RendererChangeEvent}
 580:      * to all registered listeners.
 581:      *
 582:      * @param annotation  the annotation to remove (<code>null</code> not
 583:      *                    permitted).
 584:      *
 585:      * @return A boolean to indicate whether or not the annotation was
 586:      *         successfully removed.
 587:      */
 588:     public boolean removeAnnotation(XYAnnotation annotation) {
 589:         boolean removed = this.foregroundAnnotations.remove(annotation);
 590:         removed = removed & this.backgroundAnnotations.remove(annotation);
 591:         fireChangeEvent();
 592:         return removed;
 593:     }
 594: 
 595:     /**
 596:      * Removes all annotations and sends a {@link RendererChangeEvent}
 597:      * to all registered listeners.
 598:      */
 599:     public void removeAnnotations() {
 600:         this.foregroundAnnotations.clear();
 601:         this.backgroundAnnotations.clear();
 602:         fireChangeEvent();
 603:     }
 604: 
 605:     /**
 606:      * Returns the radius of the circle used for the default entity area
 607:      * when no area is specified.
 608:      *
 609:      * @return A radius.
 610:      */
 611:     public int getDefaultEntityRadius() {
 612:         return this.defaultEntityRadius;
 613:     }
 614: 
 615:     /**
 616:      * Sets the radius of the circle used for the default entity area
 617:      * when no area is specified.
 618:      *
 619:      * @param radius  the radius.
 620:      */
 621:     public void setDefaultEntityRadius(int radius) {
 622:         this.defaultEntityRadius = radius;
 623:     }
 624: 
 625:     /**
 626:      * Returns the legend item label generator.
 627:      *
 628:      * @return The label generator (never <code>null</code>).
 629:      *
 630:      * @see #setLegendItemLabelGenerator(XYSeriesLabelGenerator)
 631:      */
 632:     public XYSeriesLabelGenerator getLegendItemLabelGenerator() {
 633:         return this.legendItemLabelGenerator;
 634:     }
 635: 
 636:     /**
 637:      * Sets the legend item label generator and sends a
 638:      * {@link RendererChangeEvent} to all registered listeners.
 639:      *
 640:      * @param generator  the generator (<code>null</code> not permitted).
 641:      *
 642:      * @see #getLegendItemLabelGenerator()
 643:      */
 644:     public void setLegendItemLabelGenerator(XYSeriesLabelGenerator generator) {
 645:         if (generator == null) {
 646:             throw new IllegalArgumentException("Null 'generator' argument.");
 647:         }
 648:         this.legendItemLabelGenerator = generator;
 649:         fireChangeEvent();
 650:     }
 651: 
 652:     /**
 653:      * Returns the legend item tool tip generator.
 654:      *
 655:      * @return The tool tip generator (possibly <code>null</code>).
 656:      *
 657:      * @see #setLegendItemToolTipGenerator(XYSeriesLabelGenerator)
 658:      */
 659:     public XYSeriesLabelGenerator getLegendItemToolTipGenerator() {
 660:         return this.legendItemToolTipGenerator;
 661:     }
 662: 
 663:     /**
 664:      * Sets the legend item tool tip generator and sends a
 665:      * {@link RendererChangeEvent} to all registered listeners.
 666:      *
 667:      * @param generator  the generator (<code>null</code> permitted).
 668:      *
 669:      * @see #getLegendItemToolTipGenerator()
 670:      */
 671:     public void setLegendItemToolTipGenerator(
 672:             XYSeriesLabelGenerator generator) {
 673:         this.legendItemToolTipGenerator = generator;
 674:         fireChangeEvent();
 675:     }
 676: 
 677:     /**
 678:      * Returns the legend item URL generator.
 679:      *
 680:      * @return The URL generator (possibly <code>null</code>).
 681:      *
 682:      * @see #setLegendItemURLGenerator(XYSeriesLabelGenerator)
 683:      */
 684:     public XYSeriesLabelGenerator getLegendItemURLGenerator() {
 685:         return this.legendItemURLGenerator;
 686:     }
 687: 
 688:     /**
 689:      * Sets the legend item URL generator and sends a
 690:      * {@link RendererChangeEvent} to all registered listeners.
 691:      *
 692:      * @param generator  the generator (<code>null</code> permitted).
 693:      *
 694:      * @see #getLegendItemURLGenerator()
 695:      */
 696:     public void setLegendItemURLGenerator(XYSeriesLabelGenerator generator) {
 697:         this.legendItemURLGenerator = generator;
 698:         fireChangeEvent();
 699:     }
 700: 
 701:     /**
 702:      * Returns the lower and upper bounds (range) of the x-values in the
 703:      * specified dataset.
 704:      *
 705:      * @param dataset  the dataset (<code>null</code> permitted).
 706:      *
 707:      * @return The range (<code>null</code> if the dataset is <code>null</code>
 708:      *         or empty).
 709:      */
 710:     public Range findDomainBounds(XYDataset dataset) {
 711:         if (dataset != null) {
 712:             return DatasetUtilities.findDomainBounds(dataset, false);
 713:         }
 714:         else {
 715:             return null;
 716:         }
 717:     }
 718: 
 719:     /**
 720:      * Returns the range of values the renderer requires to display all the
 721:      * items from the specified dataset.
 722:      *
 723:      * @param dataset  the dataset (<code>null</code> permitted).
 724:      *
 725:      * @return The range (<code>null</code> if the dataset is <code>null</code>
 726:      *         or empty).
 727:      */
 728:     public Range findRangeBounds(XYDataset dataset) {
 729:         if (dataset != null) {
 730:             return DatasetUtilities.findRangeBounds(dataset, false);
 731:         }
 732:         else {
 733:             return null;
 734:         }
 735:     }
 736: 
 737:     /**
 738:      * Returns a (possibly empty) collection of legend items for the series
 739:      * that this renderer is responsible for drawing.
 740:      *
 741:      * @return The legend item collection (never <code>null</code>).
 742:      */
 743:     public LegendItemCollection getLegendItems() {
 744:         if (this.plot == null) {
 745:             return new LegendItemCollection();
 746:         }
 747:         LegendItemCollection result = new LegendItemCollection();
 748:         int index = this.plot.getIndexOf(this);
 749:         XYDataset dataset = this.plot.getDataset(index);
 750:         if (dataset != null) {
 751:             int seriesCount = dataset.getSeriesCount();
 752:             for (int i = 0; i < seriesCount; i++) {
 753:                 if (isSeriesVisibleInLegend(i)) {
 754:                     LegendItem item = getLegendItem(index, i);
 755:                     if (item != null) {
 756:                         result.add(item);
 757:                     }
 758:                 }
 759:             }
 760: 
 761:         }
 762:         return result;
 763:     }
 764: 
 765:     /**
 766:      * Returns a default legend item for the specified series.  Subclasses
 767:      * should override this method to generate customised items.
 768:      *
 769:      * @param datasetIndex  the dataset index (zero-based).
 770:      * @param series  the series index (zero-based).
 771:      *
 772:      * @return A legend item for the series.
 773:      */
 774:     public LegendItem getLegendItem(int datasetIndex, int series) {
 775:         LegendItem result = null;
 776:         XYPlot xyplot = getPlot();
 777:         if (xyplot != null) {
 778:             XYDataset dataset = xyplot.getDataset(datasetIndex);
 779:             if (dataset != null) {
 780:                 String label = this.legendItemLabelGenerator.generateLabel(
 781:                         dataset, series);
 782:                 String description = label;
 783:                 String toolTipText = null;
 784:                 if (getLegendItemToolTipGenerator() != null) {
 785:                     toolTipText = getLegendItemToolTipGenerator().generateLabel(
 786:                             dataset, series);
 787:                 }
 788:                 String urlText = null;
 789:                 if (getLegendItemURLGenerator() != null) {
 790:                     urlText = getLegendItemURLGenerator().generateLabel(
 791:                             dataset, series);
 792:                 }
 793:                 Shape shape = lookupSeriesShape(series);
 794:                 Paint paint = lookupSeriesPaint(series);
 795:                 Paint outlinePaint = lookupSeriesOutlinePaint(series);
 796:                 Stroke outlineStroke = lookupSeriesOutlineStroke(series);
 797:                 result = new LegendItem(label, description, toolTipText,
 798:                         urlText, shape, paint, outlineStroke, outlinePaint);
 799:                 result.setSeriesKey(dataset.getSeriesKey(series));
 800:                 result.setSeriesIndex(series);
 801:                 result.setDataset(dataset);
 802:                 result.setDatasetIndex(datasetIndex);
 803:             }
 804:         }
 805:         return result;
 806:     }
 807: 
 808:     /**
 809:      * Fills a band between two values on the axis.  This can be used to color
 810:      * bands between the grid lines.
 811:      *
 812:      * @param g2  the graphics device.
 813:      * @param plot  the plot.
 814:      * @param axis  the domain axis.
 815:      * @param dataArea  the data area.
 816:      * @param start  the start value.
 817:      * @param end  the end value.
 818:      */
 819:     public void fillDomainGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis,
 820:             Rectangle2D dataArea, double start, double end) {
 821: 
 822:         double x1 = axis.valueToJava2D(start, dataArea,
 823:                 plot.getDomainAxisEdge());
 824:         double x2 = axis.valueToJava2D(end, dataArea,
 825:                 plot.getDomainAxisEdge());
 826:         Rectangle2D band;
 827:         if (plot.getOrientation() == PlotOrientation.VERTICAL) {
 828:             band = new Rectangle2D.Double(Math.min(x1, x2), dataArea.getMinY(), 
 829:                     Math.abs(x2 - x1), dataArea.getWidth());
 830:         }
 831:         else {
 832:             band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(x1, x2), 
 833:                     dataArea.getWidth(), Math.abs(x2 - x1));
 834:         }
 835:         Paint paint = plot.getDomainTickBandPaint();
 836: 
 837:         if (paint != null) {
 838:             g2.setPaint(paint);
 839:             g2.fill(band);
 840:         }
 841: 
 842:     }
 843: 
 844:     /**
 845:      * Fills a band between two values on the range axis.  This can be used to
 846:      * color bands between the grid lines.
 847:      *
 848:      * @param g2  the graphics device.
 849:      * @param plot  the plot.
 850:      * @param axis  the range axis.
 851:      * @param dataArea  the data area.
 852:      * @param start  the start value.
 853:      * @param end  the end value.
 854:      */
 855:     public void fillRangeGridBand(Graphics2D g2, XYPlot plot, ValueAxis axis,
 856:             Rectangle2D dataArea, double start, double end) {
 857: 
 858:         double y1 = axis.valueToJava2D(start, dataArea, 
 859:                 plot.getRangeAxisEdge());
 860:         double y2 = axis.valueToJava2D(end, dataArea, plot.getRangeAxisEdge());
 861:         Rectangle2D band;
 862:         if (plot.getOrientation() == PlotOrientation.VERTICAL) {
 863:             band = new Rectangle2D.Double(dataArea.getMinX(), Math.min(y1, y2),
 864:                 dataArea.getWidth(), Math.abs(y2 - y1));
 865:         }
 866:         else {
 867:             band = new Rectangle2D.Double(Math.min(y1, y2), dataArea.getMinY(),
 868:                     Math.abs(y2 - y1), dataArea.getHeight());
 869:         }
 870:         Paint paint = plot.getRangeTickBandPaint();
 871: 
 872:         if (paint != null) {
 873:             g2.setPaint(paint);
 874:             g2.fill(band);
 875:         }
 876: 
 877:     }
 878: 
 879:     /**
 880:      * Draws a grid line against the range axis.
 881:      *
 882:      * @param g2  the graphics device.
 883:      * @param plot  the plot.
 884:      * @param axis  the value axis.
 885:      * @param dataArea  the area for plotting data (not yet adjusted for any
 886:      *                  3D effect).
 887:      * @param value  the value at which the grid line should be drawn.
 888:      */
 889:     public void drawDomainGridLine(Graphics2D g2,
 890:                                    XYPlot plot,
 891:                                    ValueAxis axis,
 892:                                    Rectangle2D dataArea,
 893:                                    double value) {
 894: 
 895:         Range range = axis.getRange();
 896:         if (!range.contains(value)) {
 897:             return;
 898:         }
 899: 
 900:         PlotOrientation orientation = plot.getOrientation();
 901:         double v = axis.valueToJava2D(value, dataArea,
 902:                 plot.getDomainAxisEdge());
 903:         Line2D line = null;
 904:         if (orientation == PlotOrientation.HORIZONTAL) {
 905:             line = new Line2D.Double(dataArea.getMinX(), v,
 906:                     dataArea.getMaxX(), v);
 907:         }
 908:         else if (orientation == PlotOrientation.VERTICAL) {
 909:             line = new Line2D.Double(v, dataArea.getMinY(), v,
 910:                     dataArea.getMaxY());
 911:         }
 912: 
 913:         Paint paint = plot.getDomainGridlinePaint();
 914:         Stroke stroke = plot.getDomainGridlineStroke();
 915:         g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
 916:         g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
 917:         g2.draw(line);
 918: 
 919:     }
 920: 
 921:     /**
 922:      * Draws a line perpendicular to the domain axis.
 923:      *
 924:      * @param g2  the graphics device.
 925:      * @param plot  the plot.
 926:      * @param axis  the value axis.
 927:      * @param dataArea  the area for plotting data (not yet adjusted for any 3D
 928:      *                  effect).
 929:      * @param value  the value at which the grid line should be drawn.
 930:      * @param paint  the paint.
 931:      * @param stroke  the stroke.
 932:      * 
 933:      * @since 1.0.5
 934:      */
 935:     public void drawDomainLine(Graphics2D g2, XYPlot plot, ValueAxis axis,
 936:             Rectangle2D dataArea, double value, Paint paint, Stroke stroke) {
 937: 
 938:         Range range = axis.getRange();
 939:         if (!range.contains(value)) {
 940:             return;
 941:         }
 942: 
 943:         PlotOrientation orientation = plot.getOrientation();
 944:         Line2D line = null;
 945:         double v = axis.valueToJava2D(value, dataArea, 
 946:                 plot.getDomainAxisEdge());
 947:         if (orientation == PlotOrientation.HORIZONTAL) {
 948:             line = new Line2D.Double(dataArea.getMinX(), v, dataArea.getMaxX(), 
 949:                     v);
 950:         }
 951:         else if (orientation == PlotOrientation.VERTICAL) {
 952:             line = new Line2D.Double(v, dataArea.getMinY(), v, 
 953:                     dataArea.getMaxY());
 954:         }
 955: 
 956:         g2.setPaint(paint);
 957:         g2.setStroke(stroke);
 958:         g2.draw(line);
 959: 
 960:     }
 961: 
 962:     /**
 963:      * Draws a line perpendicular to the range axis.
 964:      *
 965:      * @param g2  the graphics device.
 966:      * @param plot  the plot.
 967:      * @param axis  the value axis.
 968:      * @param dataArea  the area for plotting data (not yet adjusted for any 3D
 969:      *                  effect).
 970:      * @param value  the value at which the grid line should be drawn.
 971:      * @param paint  the paint.
 972:      * @param stroke  the stroke.
 973:      */
 974:     public void drawRangeLine(Graphics2D g2,
 975:                               XYPlot plot,
 976:                               ValueAxis axis,
 977:                               Rectangle2D dataArea,
 978:                               double value,
 979:                               Paint paint,
 980:                               Stroke stroke) {
 981: 
 982:         Range range = axis.getRange();
 983:         if (!range.contains(value)) {
 984:             return;
 985:         }
 986: 
 987:         PlotOrientation orientation = plot.getOrientation();
 988:         Line2D line = null;
 989:         double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
 990:         if (orientation == PlotOrientation.HORIZONTAL) {
 991:             line = new Line2D.Double(v, dataArea.getMinY(), v,
 992:                     dataArea.getMaxY());
 993:         }
 994:         else if (orientation == PlotOrientation.VERTICAL) {
 995:             line = new Line2D.Double(dataArea.getMinX(), v,
 996:                     dataArea.getMaxX(), v);
 997:         }
 998: 
 999:         g2.setPaint(paint);
1000:         g2.setStroke(stroke);
1001:         g2.draw(line);
1002: 
1003:     }
1004: 
1005:     /**
1006:      * Draws a vertical line on the chart to represent a 'range marker'.
1007:      *
1008:      * @param g2  the graphics device.
1009:      * @param plot  the plot.
1010:      * @param domainAxis  the domain axis.
1011:      * @param marker  the marker line.
1012:      * @param dataArea  the axis data area.
1013:      */
1014:     public void drawDomainMarker(Graphics2D g2,
1015:                                  XYPlot plot,
1016:                                  ValueAxis domainAxis,
1017:                                  Marker marker,
1018:                                  Rectangle2D dataArea) {
1019: 
1020:         if (marker instanceof ValueMarker) {
1021:             ValueMarker vm = (ValueMarker) marker;
1022:             double value = vm.getValue();
1023:             Range range = domainAxis.getRange();
1024:             if (!range.contains(value)) {
1025:                 return;
1026:             }
1027: 
1028:             double v = domainAxis.valueToJava2D(value, dataArea,
1029:                     plot.getDomainAxisEdge());
1030: 
1031:             PlotOrientation orientation = plot.getOrientation();
1032:             Line2D line = null;
1033:             if (orientation == PlotOrientation.HORIZONTAL) {
1034:                 line = new Line2D.Double(dataArea.getMinX(), v,
1035:                         dataArea.getMaxX(), v);
1036:             }
1037:             else if (orientation == PlotOrientation.VERTICAL) {
1038:                 line = new Line2D.Double(v, dataArea.getMinY(), v,
1039:                         dataArea.getMaxY());
1040:             }
1041: 
1042:             final Composite originalComposite = g2.getComposite();
1043:             g2.setComposite(AlphaComposite.getInstance(
1044:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1045:             g2.setPaint(marker.getPaint());
1046:             g2.setStroke(marker.getStroke());
1047:             g2.draw(line);
1048: 
1049:             String label = marker.getLabel();
1050:             RectangleAnchor anchor = marker.getLabelAnchor();
1051:             if (label != null) {
1052:                 Font labelFont = marker.getLabelFont();
1053:                 g2.setFont(labelFont);
1054:                 g2.setPaint(marker.getLabelPaint());
1055:                 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
1056:                         g2, orientation, dataArea, line.getBounds2D(),
1057:                         marker.getLabelOffset(),
1058:                         LengthAdjustmentType.EXPAND, anchor);
1059:                 TextUtilities.drawAlignedString(label, g2,
1060:                         (float) coordinates.getX(), (float) coordinates.getY(),
1061:                         marker.getLabelTextAnchor());
1062:             }
1063:             g2.setComposite(originalComposite);
1064:         }
1065:         else if (marker instanceof IntervalMarker) {
1066:             IntervalMarker im = (IntervalMarker) marker;
1067:             double start = im.getStartValue();
1068:             double end = im.getEndValue();
1069:             Range range = domainAxis.getRange();
1070:             if (!(range.intersects(start, end))) {
1071:                 return;
1072:             }
1073: 
1074:             double start2d = domainAxis.valueToJava2D(start, dataArea,
1075:                     plot.getDomainAxisEdge());
1076:             double end2d = domainAxis.valueToJava2D(end, dataArea,
1077:                     plot.getDomainAxisEdge());
1078:             double low = Math.min(start2d, end2d);
1079:             double high = Math.max(start2d, end2d);
1080: 
1081:             PlotOrientation orientation = plot.getOrientation();
1082:             Rectangle2D rect = null;
1083:             if (orientation == PlotOrientation.HORIZONTAL) {
1084:                 // clip top and bottom bounds to data area
1085:                 low = Math.max(low, dataArea.getMinY());
1086:                 high = Math.min(high, dataArea.getMaxY());
1087:                 rect = new Rectangle2D.Double(dataArea.getMinX(),
1088:                         low, dataArea.getWidth(),
1089:                         high - low);
1090:             }
1091:             else if (orientation == PlotOrientation.VERTICAL) {
1092:                 // clip left and right bounds to data area
1093:                 low = Math.max(low, dataArea.getMinX());
1094:                 high = Math.min(high, dataArea.getMaxX());
1095:                 rect = new Rectangle2D.Double(low,
1096:                         dataArea.getMinY(), high - low,
1097:                         dataArea.getHeight());
1098:             }
1099: 
1100:             final Composite originalComposite = g2.getComposite();
1101:             g2.setComposite(AlphaComposite.getInstance(
1102:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1103:             Paint p = marker.getPaint();
1104:             if (p instanceof GradientPaint) {
1105:                 GradientPaint gp = (GradientPaint) p;
1106:                 GradientPaintTransformer t = im.getGradientPaintTransformer();
1107:                 if (t != null) {
1108:                     gp = t.transform(gp, rect);
1109:                 }
1110:                 g2.setPaint(gp);
1111:             }
1112:             else {
1113:                 g2.setPaint(p);
1114:             }
1115:             g2.fill(rect);
1116: 
1117:             // now draw the outlines, if visible...
1118:             if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1119:                 if (orientation == PlotOrientation.VERTICAL) {
1120:                     Line2D line = new Line2D.Double();
1121:                     double y0 = dataArea.getMinY();
1122:                     double y1 = dataArea.getMaxY();
1123:                     g2.setPaint(im.getOutlinePaint());
1124:                     g2.setStroke(im.getOutlineStroke());
1125:                     if (range.contains(start)) {
1126:                         line.setLine(start2d, y0, start2d, y1);
1127:                         g2.draw(line);
1128:                     }
1129:                     if (range.contains(end)) {
1130:                         line.setLine(end2d, y0, end2d, y1);
1131:                         g2.draw(line);
1132:                     }
1133:                 }
1134:                 else { // PlotOrientation.HORIZONTAL
1135:                     Line2D line = new Line2D.Double();
1136:                     double x0 = dataArea.getMinX();
1137:                     double x1 = dataArea.getMaxX();
1138:                     g2.setPaint(im.getOutlinePaint());
1139:                     g2.setStroke(im.getOutlineStroke());
1140:                     if (range.contains(start)) {
1141:                         line.setLine(x0, start2d, x1, start2d);
1142:                         g2.draw(line);
1143:                     }
1144:                     if (range.contains(end)) {
1145:                         line.setLine(x0, end2d, x1, end2d);
1146:                         g2.draw(line);
1147:                     }
1148:                 }
1149:             }
1150: 
1151:             String label = marker.getLabel();
1152:             RectangleAnchor anchor = marker.getLabelAnchor();
1153:             if (label != null) {
1154:                 Font labelFont = marker.getLabelFont();
1155:                 g2.setFont(labelFont);
1156:                 g2.setPaint(marker.getLabelPaint());
1157:                 Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
1158:                         g2, orientation, dataArea, rect,
1159:                         marker.getLabelOffset(), marker.getLabelOffsetType(),
1160:                         anchor);
1161:                 TextUtilities.drawAlignedString(label, g2,
1162:                         (float) coordinates.getX(), (float) coordinates.getY(),
1163:                         marker.getLabelTextAnchor());
1164:             }
1165:             g2.setComposite(originalComposite);
1166: 
1167:         }
1168: 
1169:     }
1170: 
1171:     /**
1172:      * Calculates the (x, y) coordinates for drawing a marker label.
1173:      *
1174:      * @param g2  the graphics device.
1175:      * @param orientation  the plot orientation.
1176:      * @param dataArea  the data area.
1177:      * @param markerArea  the rectangle surrounding the marker area.
1178:      * @param markerOffset  the marker label offset.
1179:      * @param labelOffsetType  the label offset type.
1180:      * @param anchor  the label anchor.
1181:      *
1182:      * @return The coordinates for drawing the marker label.
1183:      */
1184:     protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2,
1185:             PlotOrientation orientation,
1186:             Rectangle2D dataArea,
1187:             Rectangle2D markerArea,
1188:             RectangleInsets markerOffset,
1189:             LengthAdjustmentType labelOffsetType,
1190:             RectangleAnchor anchor) {
1191: 
1192:         Rectangle2D anchorRect = null;
1193:         if (orientation == PlotOrientation.HORIZONTAL) {
1194:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1195:                     LengthAdjustmentType.CONTRACT, labelOffsetType);
1196:         }
1197:         else if (orientation == PlotOrientation.VERTICAL) {
1198:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1199:                     labelOffsetType, LengthAdjustmentType.CONTRACT);
1200:         }
1201:         return RectangleAnchor.coordinates(anchorRect, anchor);
1202: 
1203:     }
1204: 
1205:     /**
1206:      * Draws a horizontal line across the chart to represent a 'range marker'.
1207:      *
1208:      * @param g2  the graphics device.
1209:      * @param plot  the plot.
1210:      * @param rangeAxis  the range axis.
1211:      * @param marker  the marker line.
1212:      * @param dataArea  the axis data area.
1213:      */
1214:     public void drawRangeMarker(Graphics2D g2,
1215:                                 XYPlot plot,
1216:                                 ValueAxis rangeAxis,
1217:                                 Marker marker,
1218:                                 Rectangle2D dataArea) {
1219: 
1220:         if (marker instanceof ValueMarker) {
1221:             ValueMarker vm = (ValueMarker) marker;
1222:             double value = vm.getValue();
1223:             Range range = rangeAxis.getRange();
1224:             if (!range.contains(value)) {
1225:                 return;
1226:             }
1227: 
1228:             double v = rangeAxis.valueToJava2D(value, dataArea,
1229:                     plot.getRangeAxisEdge());
1230:             PlotOrientation orientation = plot.getOrientation();
1231:             Line2D line = null;
1232:             if (orientation == PlotOrientation.HORIZONTAL) {
1233:                 line = new Line2D.Double(v, dataArea.getMinY(), v,
1234:                         dataArea.getMaxY());
1235:             }
1236:             else if (orientation == PlotOrientation.VERTICAL) {
1237:                 line = new Line2D.Double(dataArea.getMinX(), v,
1238:                         dataArea.getMaxX(), v);
1239:             }
1240: 
1241:             final Composite originalComposite = g2.getComposite();
1242:             g2.setComposite(AlphaComposite.getInstance(
1243:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1244:             g2.setPaint(marker.getPaint());
1245:             g2.setStroke(marker.getStroke());
1246:             g2.draw(line);
1247: 
1248:             String label = marker.getLabel();
1249:             RectangleAnchor anchor = marker.getLabelAnchor();
1250:             if (label != null) {
1251:                 Font labelFont = marker.getLabelFont();
1252:                 g2.setFont(labelFont);
1253:                 g2.setPaint(marker.getLabelPaint());
1254:                 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1255:                         g2, orientation, dataArea, line.getBounds2D(),
1256:                         marker.getLabelOffset(),
1257:                         LengthAdjustmentType.EXPAND, anchor);
1258:                 TextUtilities.drawAlignedString(label, g2,
1259:                         (float) coordinates.getX(), (float) coordinates.getY(),
1260:                         marker.getLabelTextAnchor());
1261:             }
1262:             g2.setComposite(originalComposite);
1263:         }
1264:         else if (marker instanceof IntervalMarker) {
1265:             IntervalMarker im = (IntervalMarker) marker;
1266:             double start = im.getStartValue();
1267:             double end = im.getEndValue();
1268:             Range range = rangeAxis.getRange();
1269:             if (!(range.intersects(start, end))) {
1270:                 return;
1271:             }
1272: 
1273:             double start2d = rangeAxis.valueToJava2D(start, dataArea,
1274:                     plot.getRangeAxisEdge());
1275:             double end2d = rangeAxis.valueToJava2D(end, dataArea,
1276:                     plot.getRangeAxisEdge());
1277:             double low = Math.min(start2d, end2d);
1278:             double high = Math.max(start2d, end2d);
1279: 
1280:             PlotOrientation orientation = plot.getOrientation();
1281:             Rectangle2D rect = null;
1282:             if (orientation == PlotOrientation.HORIZONTAL) {
1283:                 // clip left and right bounds to data area
1284:                 low = Math.max(low, dataArea.getMinX());
1285:                 high = Math.min(high, dataArea.getMaxX());
1286:                 rect = new Rectangle2D.Double(low,
1287:                         dataArea.getMinY(), high - low,
1288:                         dataArea.getHeight());
1289:             }
1290:             else if (orientation == PlotOrientation.VERTICAL) {
1291:                 // clip top and bottom bounds to data area
1292:                 low = Math.max(low, dataArea.getMinY());
1293:                 high = Math.min(high, dataArea.getMaxY());
1294:                 rect = new Rectangle2D.Double(dataArea.getMinX(),
1295:                         low, dataArea.getWidth(),
1296:                         high - low);
1297:             }
1298: 
1299:             final Composite originalComposite = g2.getComposite();
1300:             g2.setComposite(AlphaComposite.getInstance(
1301:                     AlphaComposite.SRC_OVER, marker.getAlpha()));
1302:             Paint p = marker.getPaint();
1303:             if (p instanceof GradientPaint) {
1304:                 GradientPaint gp = (GradientPaint) p;
1305:                 GradientPaintTransformer t = im.getGradientPaintTransformer();
1306:                 if (t != null) {
1307:                     gp = t.transform(gp, rect);
1308:                 }
1309:                 g2.setPaint(gp);
1310:             }
1311:             else {
1312:                 g2.setPaint(p);
1313:             }
1314:             g2.fill(rect);
1315: 
1316:             // now draw the outlines, if visible...
1317:             if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1318:                 if (orientation == PlotOrientation.VERTICAL) {
1319:                     Line2D line = new Line2D.Double();
1320:                     double x0 = dataArea.getMinX();
1321:                     double x1 = dataArea.getMaxX();
1322:                     g2.setPaint(im.getOutlinePaint());
1323:                     g2.setStroke(im.getOutlineStroke());
1324:                     if (range.contains(start)) {
1325:                         line.setLine(x0, start2d, x1, start2d);
1326:                         g2.draw(line);
1327:                     }
1328:                     if (range.contains(end)) {
1329:                         line.setLine(x0, end2d, x1, end2d);
1330:                         g2.draw(line);
1331:                     }
1332:                 }
1333:                 else { // PlotOrientation.HORIZONTAL
1334:                     Line2D line = new Line2D.Double();
1335:                     double y0 = dataArea.getMinY();
1336:                     double y1 = dataArea.getMaxY();
1337:                     g2.setPaint(im.getOutlinePaint());
1338:                     g2.setStroke(im.getOutlineStroke());
1339:                     if (range.contains(start)) {
1340:                         line.setLine(start2d, y0, start2d, y1);
1341:                         g2.draw(line);
1342:                     }
1343:                     if (range.contains(end)) {
1344:                         line.setLine(end2d, y0, end2d, y1);
1345:                         g2.draw(line);
1346:                     }
1347:                 }
1348:             }
1349: 
1350:             String label = marker.getLabel();
1351:             RectangleAnchor anchor = marker.getLabelAnchor();
1352:             if (label != null) {
1353:                 Font labelFont = marker.getLabelFont();
1354:                 g2.setFont(labelFont);
1355:                 g2.setPaint(marker.getLabelPaint());
1356:                 Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
1357:                         g2, orientation, dataArea, rect,
1358:                         marker.getLabelOffset(), marker.getLabelOffsetType(),
1359:                         anchor);
1360:                 TextUtilities.drawAlignedString(label, g2,
1361:                         (float) coordinates.getX(), (float) coordinates.getY(),
1362:                         marker.getLabelTextAnchor());
1363:             }
1364:             g2.setComposite(originalComposite);
1365:         }
1366:     }
1367: 
1368:     /**
1369:      * Calculates the (x, y) coordinates for drawing a marker label.
1370:      *
1371:      * @param g2  the graphics device.
1372:      * @param orientation  the plot orientation.
1373:      * @param dataArea  the data area.
1374:      * @param markerArea  the marker area.
1375:      * @param markerOffset  the marker offset.
1376:      * @param labelOffsetForRange  ??
1377:      * @param anchor  the label anchor.
1378:      *
1379:      * @return The coordinates for drawing the marker label.
1380:      */
1381:     private Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2,
1382:                                       PlotOrientation orientation,
1383:                                       Rectangle2D dataArea,
1384:                                       Rectangle2D markerArea,
1385:                                       RectangleInsets markerOffset,
1386:                                       LengthAdjustmentType labelOffsetForRange,
1387:                                       RectangleAnchor anchor) {
1388: 
1389:         Rectangle2D anchorRect = null;
1390:         if (orientation == PlotOrientation.HORIZONTAL) {
1391:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1392:                     labelOffsetForRange, LengthAdjustmentType.CONTRACT);
1393:         }
1394:         else if (orientation == PlotOrientation.VERTICAL) {
1395:             anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1396:                     LengthAdjustmentType.CONTRACT, labelOffsetForRange);
1397:         }
1398:         return RectangleAnchor.coordinates(anchorRect, anchor);
1399: 
1400:     }
1401: 
1402:     /**
1403:      * Returns a clone of the renderer.
1404:      *
1405:      * @return A clone.
1406:      *
1407:      * @throws CloneNotSupportedException if the renderer does not support
1408:      *         cloning.
1409:      */
1410:     protected Object clone() throws CloneNotSupportedException {
1411:         AbstractXYItemRenderer clone = (AbstractXYItemRenderer) super.clone();
1412:         // 'plot' : just retain reference, not a deep copy
1413: 
1414:         if (this.itemLabelGenerator != null
1415:                 && this.itemLabelGenerator instanceof PublicCloneable) {
1416:             PublicCloneable pc = (PublicCloneable) this.itemLabelGenerator;
1417:             clone.itemLabelGenerator = (XYItemLabelGenerator) pc.clone();
1418:         }
1419:         clone.itemLabelGeneratorList
1420:                 = (ObjectList) this.itemLabelGeneratorList.clone();
1421:         if (this.baseItemLabelGenerator != null
1422:                 && this.baseItemLabelGenerator instanceof PublicCloneable) {
1423:             PublicCloneable pc = (PublicCloneable) this.baseItemLabelGenerator;
1424:             clone.baseItemLabelGenerator = (XYItemLabelGenerator) pc.clone();
1425:         }
1426: 
1427:         if (this.toolTipGenerator != null
1428:                 && this.toolTipGenerator instanceof PublicCloneable) {
1429:             PublicCloneable pc = (PublicCloneable) this.toolTipGenerator;
1430:             clone.toolTipGenerator = (XYToolTipGenerator) pc.clone();
1431:         }
1432:         clone.toolTipGeneratorList
1433:                 = (ObjectList) this.toolTipGeneratorList.clone();
1434:         if (this.baseToolTipGenerator != null
1435:                 && this.baseToolTipGenerator instanceof PublicCloneable) {
1436:             PublicCloneable pc = (PublicCloneable) this.baseToolTipGenerator;
1437:             clone.baseToolTipGenerator = (XYToolTipGenerator) pc.clone();
1438:         }
1439: 
1440:         if (clone.legendItemLabelGenerator instanceof PublicCloneable) {
1441:             clone.legendItemLabelGenerator = (XYSeriesLabelGenerator)
1442:                     ObjectUtilities.clone(this.legendItemLabelGenerator);
1443:         }
1444:         if (clone.legendItemToolTipGenerator instanceof PublicCloneable) {
1445:             clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator)
1446:                     ObjectUtilities.clone(this.legendItemToolTipGenerator);
1447:         }
1448:         if (clone.legendItemURLGenerator instanceof PublicCloneable) {
1449:             clone.legendItemURLGenerator = (XYSeriesLabelGenerator)
1450:                     ObjectUtilities.clone(this.legendItemURLGenerator);
1451:         }
1452: 
1453:         clone.foregroundAnnotations = (List) ObjectUtilities.deepClone(
1454:                 this.foregroundAnnotations);
1455:         clone.backgroundAnnotations = (List) ObjectUtilities.deepClone(
1456:                 this.backgroundAnnotations);
1457: 
1458:         if (clone.legendItemLabelGenerator instanceof PublicCloneable) {
1459:             clone.legendItemLabelGenerator = (XYSeriesLabelGenerator)
1460:                     ObjectUtilities.clone(this.legendItemLabelGenerator);
1461:         }
1462:         if (clone.legendItemToolTipGenerator instanceof PublicCloneable) {
1463:             clone.legendItemToolTipGenerator = (XYSeriesLabelGenerator)
1464:                     ObjectUtilities.clone(this.legendItemToolTipGenerator);
1465:         }
1466:         if (clone.legendItemURLGenerator instanceof PublicCloneable) {
1467:             clone.legendItemURLGenerator = (XYSeriesLabelGenerator)
1468:                     ObjectUtilities.clone(this.legendItemURLGenerator);
1469:         }
1470: 
1471:         return clone;
1472:     }
1473: 
1474:     /**
1475:      * Tests this renderer for equality with another object.
1476:      *
1477:      * @param obj  the object (<code>null</code> permitted).
1478:      *
1479:      * @return <code>true</code> or <code>false</code>.
1480:      */
1481:     public boolean equals(Object obj) {
1482:         if (obj == this) {
1483:             return true;
1484:         }
1485:         if (!(obj instanceof AbstractXYItemRenderer)) {
1486:             return false;
1487:         }
1488:         AbstractXYItemRenderer that = (AbstractXYItemRenderer) obj;
1489:         if (!ObjectUtilities.equal(this.itemLabelGenerator,
1490:                 that.itemLabelGenerator)) {
1491:             return false;
1492:         }
1493:         if (!this.itemLabelGeneratorList.equals(that.itemLabelGeneratorList)) {
1494:             return false;
1495:         }
1496:         if (!ObjectUtilities.equal(this.baseItemLabelGenerator,
1497:                 that.baseItemLabelGenerator)) {
1498:             return false;
1499:         }
1500:         if (!ObjectUtilities.equal(this.toolTipGenerator,
1501:                 that.toolTipGenerator)) {
1502:             return false;
1503:         }
1504:         if (!this.toolTipGeneratorList.equals(that.toolTipGeneratorList)) {
1505:             return false;
1506:         }
1507:         if (!ObjectUtilities.equal(this.baseToolTipGenerator,
1508:                 that.baseToolTipGenerator)) {
1509:             return false;
1510:         }
1511:         if (!ObjectUtilities.equal(this.urlGenerator, that.urlGenerator)) {
1512:             return false;
1513:         }
1514:         if (!this.foregroundAnnotations.equals(that.foregroundAnnotations)) {
1515:             return false;
1516:         }
1517:         if (!this.backgroundAnnotations.equals(that.backgroundAnnotations)) {
1518:             return false;
1519:         }
1520:         if (this.defaultEntityRadius != that.defaultEntityRadius) {
1521:             return false;
1522:         }
1523:         if (!ObjectUtilities.equal(this.legendItemLabelGenerator,
1524:                 that.legendItemLabelGenerator)) {
1525:             return false;
1526:         }
1527:         if (!ObjectUtilities.equal(this.legendItemToolTipGenerator,
1528:                 that.legendItemToolTipGenerator)) {
1529:             return false;
1530:         }
1531:         if (!ObjectUtilities.equal(this.legendItemURLGenerator,
1532:                 that.legendItemURLGenerator)) {
1533:             return false;
1534:         }
1535:         return super.equals(obj);
1536:     }
1537: 
1538:     /**
1539:      * Returns the drawing supplier from the plot.
1540:      *
1541:      * @return The drawing supplier (possibly <code>null</code>).
1542:      */
1543:     public DrawingSupplier getDrawingSupplier() {
1544:         DrawingSupplier result = null;
1545:         XYPlot p = getPlot();
1546:         if (p != null) {
1547:             result = p.getDrawingSupplier();
1548:         }
1549:         return result;
1550:     }
1551: 
1552:     /**
1553:      * Considers the current (x, y) coordinate and updates the crosshair point
1554:      * if it meets the criteria (usually means the (x, y) coordinate is the
1555:      * closest to the anchor point so far).
1556:      *
1557:      * @param crosshairState  the crosshair state (<code>null</code> permitted,
1558:      *                        but the method does nothing in that case).
1559:      * @param x  the x-value (in data space).
1560:      * @param y  the y-value (in data space).
1561:      * @param transX  the x-value translated to Java2D space.
1562:      * @param transY  the y-value translated to Java2D space.
1563:      * @param orientation  the plot orientation (<code>null</code> not
1564:      *                     permitted).
1565:      *
1566:      * @deprecated Use {@link #updateCrosshairValues(CrosshairState, double,
1567:      *         double, int, int, double, double, PlotOrientation)} -- see bug
1568:      *         report 1086307.
1569:      */
1570:     protected void updateCrosshairValues(CrosshairState crosshairState,
1571:             double x, double y, double transX, double transY,
1572:             PlotOrientation orientation) {
1573:         updateCrosshairValues(crosshairState, x, y, 0, 0, transX, transY,
1574:                 orientation);
1575:     }
1576: 
1577:     /**
1578:      * Considers the current (x, y) coordinate and updates the crosshair point
1579:      * if it meets the criteria (usually means the (x, y) coordinate is the
1580:      * closest to the anchor point so far).
1581:      *
1582:      * @param crosshairState  the crosshair state (<code>null</code> permitted,
1583:      *                        but the method does nothing in that case).
1584:      * @param x  the x-value (in data space).
1585:      * @param y  the y-value (in data space).
1586:      * @param domainAxisIndex  the index of the domain axis for the point.
1587:      * @param rangeAxisIndex  the index of the range axis for the point.
1588:      * @param transX  the x-value translated to Java2D space.
1589:      * @param transY  the y-value translated to Java2D space.
1590:      * @param orientation  the plot orientation (<code>null</code> not
1591:      *                     permitted).
1592:      *
1593:      * @since 1.0.4
1594:      */
1595:     protected void updateCrosshairValues(CrosshairState crosshairState,
1596:             double x, double y, int domainAxisIndex, int rangeAxisIndex,
1597:             double transX, double transY, PlotOrientation orientation) {
1598: 
1599:         if (orientation == null) {
1600:             throw new IllegalArgumentException("Null 'orientation' argument.");
1601:         }
1602: 
1603:         if (crosshairState != null) {
1604:             // do we need to update the crosshair values?
1605:             if (this.plot.isDomainCrosshairLockedOnData()) {
1606:                 if (this.plot.isRangeCrosshairLockedOnData()) {
1607:                     // both axes
1608:                     crosshairState.updateCrosshairPoint(x, y, domainAxisIndex,
1609:                             rangeAxisIndex, transX, transY, orientation);
1610:                 }
1611:                 else {
1612:                     // just the domain axis...
1613:                     crosshairState.updateCrosshairX(x, domainAxisIndex);
1614:                 }
1615:             }
1616:             else {
1617:                 if (this.plot.isRangeCrosshairLockedOnData()) {
1618:                     // just the range axis...
1619:                     crosshairState.updateCrosshairY(y, rangeAxisIndex);
1620:                 }
1621:             }
1622:         }
1623: 
1624:     }
1625: 
1626:     /**
1627:      * Draws an item label.
1628:      *
1629:      * @param g2  the graphics device.
1630:      * @param orientation  the orientation.
1631:      * @param dataset  the dataset.
1632:      * @param series  the series index (zero-based).
1633:      * @param item  the item index (zero-based).
1634:      * @param x  the x coordinate (in Java2D space).
1635:      * @param y  the y coordinate (in Java2D space).
1636:      * @param negative  indicates a negative value (which affects the item
1637:      *                  label position).
1638:      */
1639:     protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation,
1640:             XYDataset dataset, int series, int item, double x, double y,
1641:             boolean negative) {
1642: 
1643:         XYItemLabelGenerator generator = getItemLabelGenerator(series, item);
1644:         if (generator != null) {
1645:             Font labelFont = getItemLabelFont(series, item);
1646:             Paint paint = getItemLabelPaint(series, item);
1647:             g2.setFont(labelFont);
1648:             g2.setPaint(paint);
1649:             String label = generator.generateLabel(dataset, series, item);
1650: 
1651:             // get the label position..
1652:             ItemLabelPosition position = null;
1653:             if (!negative) {
1654:                 position = getPositiveItemLabelPosition(series, item);
1655:             }
1656:             else {
1657:                 position = getNegativeItemLabelPosition(series, item);
1658:             }
1659: 
1660:             // work out the label anchor point...
1661:             Point2D anchorPoint = calculateLabelAnchorPoint(
1662:                     position.getItemLabelAnchor(), x, y, orientation);
1663:             TextUtilities.drawRotatedString(label, g2,
1664:                     (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1665:                     position.getTextAnchor(), position.getAngle(),
1666:                     position.getRotationAnchor());
1667:         }
1668: 
1669:     }
1670: 
1671:     /**
1672:      * Draws all the annotations for the specified layer.
1673:      *
1674:      * @param g2  the graphics device.
1675:      * @param dataArea  the data area.
1676:      * @param domainAxis  the domain axis.
1677:      * @param rangeAxis  the range axis.
1678:      * @param layer  the layer.
1679:      * @param info  the plot rendering info.
1680:      */
1681:     public void drawAnnotations(Graphics2D g2,
1682:                                 Rectangle2D dataArea,
1683:                                 ValueAxis domainAxis,
1684:                                 ValueAxis rangeAxis,
1685:                                 Layer layer,
1686:                                 PlotRenderingInfo info) {
1687: 
1688:         Iterator iterator = null;
1689:         if (layer.equals(Layer.FOREGROUND)) {
1690:             iterator = this.foregroundAnnotations.iterator();
1691:         }
1692:         else if (layer.equals(Layer.BACKGROUND)) {
1693:             iterator = this.backgroundAnnotations.iterator();
1694:         }
1695:         else {
1696:             // should not get here
1697:             throw new RuntimeException("Unknown layer.");
1698:         }
1699:         while (iterator.hasNext()) {
1700:             XYAnnotation annotation = (XYAnnotation) iterator.next();
1701:             annotation.draw(g2, this.plot, dataArea, domainAxis, rangeAxis,
1702:                     0, info);
1703:         }
1704: 
1705:     }
1706: 
1707:     /**
1708:      * Adds an entity to the collection.
1709:      *
1710:      * @param entities  the entity collection being populated.
1711:      * @param area  the entity area (if <code>null</code> a default will be
1712:      *              used).
1713:      * @param dataset  the dataset.
1714:      * @param series  the series.
1715:      * @param item  the item.
1716:      * @param entityX  the entity's center x-coordinate in user space.
1717:      * @param entityY  the entity's center y-coordinate in user space.
1718:      */
1719:     protected void addEntity(EntityCollection entities, Shape area,
1720:                              XYDataset dataset, int series, int item,
1721:                              double entityX, double entityY) {
1722:         if (!getItemCreateEntity(series, item)) {
1723:             return;
1724:         }
1725:         if (area == null) {
1726:             area = new Ellipse2D.Double(entityX - this.defaultEntityRadius,
1727:                     entityY - this.defaultEntityRadius,
1728:                     this.defaultEntityRadius * 2, this.defaultEntityRadius * 2);
1729:         }
1730:         String tip = null;
1731:         XYToolTipGenerator generator = getToolTipGenerator(series, item);
1732:         if (generator != null) {
1733:             tip = generator.generateToolTip(dataset, series, item);
1734:         }
1735:         String url = null;
1736:         if (getURLGenerator() != null) {
1737:             url = getURLGenerator().generateURL(dataset, series, item);
1738:         }
1739:         XYItemEntity entity = new XYItemEntity(area, dataset, series, item,
1740:                 tip, url);
1741:         entities.add(entity);
1742:     }
1743: 
1744: }