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