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