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