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