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