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