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