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