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