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