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