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