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