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