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