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