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