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