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