Source for org.jfree.chart.renderer.category.AbstractCategoryItemRenderer

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