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