001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, 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-present, 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;
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 = 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}
667     *         or empty).
668     */
669    protected Range findRangeBounds(CategoryDataset dataset,
670            boolean includeInterval) {
671        if (dataset == null) {
672            return null;
673        }
674        if (getDataBoundsIncludesVisibleSeriesOnly()) {
675            List<Comparable> visibleSeriesKeys = new ArrayList<>();
676            int seriesCount = dataset.getRowCount();
677            for (int s = 0; s < seriesCount; s++) {
678                if (isSeriesVisible(s)) {
679                    visibleSeriesKeys.add(dataset.getRowKey(s));
680                }
681            }
682            return DatasetUtils.findRangeBounds(dataset,
683                    visibleSeriesKeys, includeInterval);
684        }
685        else {
686            return DatasetUtils.findRangeBounds(dataset, includeInterval);
687        }
688    }
689
690    /**
691     * Returns the Java2D coordinate for the middle of the specified data item.
692     *
693     * @param rowKey  the row key.
694     * @param columnKey  the column key.
695     * @param dataset  the dataset.
696     * @param axis  the axis.
697     * @param area  the data area.
698     * @param edge  the edge along which the axis lies.
699     *
700     * @return The Java2D coordinate for the middle of the item.
701     */
702    @Override
703    public double getItemMiddle(Comparable rowKey, Comparable columnKey,
704            CategoryDataset dataset, CategoryAxis axis, Rectangle2D area,
705            RectangleEdge edge) {
706        return axis.getCategoryMiddle(columnKey, dataset.getColumnKeys(), area,
707                edge);
708    }
709
710    /**
711     * Draws a background for the data area.  The default implementation just
712     * gets the plot to draw the background, but some renderers will override
713     * this behaviour.
714     *
715     * @param g2  the graphics device.
716     * @param plot  the plot.
717     * @param dataArea  the data area.
718     */
719    @Override
720    public void drawBackground(Graphics2D g2, CategoryPlot plot,
721            Rectangle2D dataArea) {
722        plot.drawBackground(g2, dataArea);
723    }
724
725    /**
726     * Draws an outline for the data area.  The default implementation just
727     * gets the plot to draw the outline, but some renderers will override this
728     * behaviour.
729     *
730     * @param g2  the graphics device.
731     * @param plot  the plot.
732     * @param dataArea  the data area.
733     */
734    @Override
735    public void drawOutline(Graphics2D g2, CategoryPlot plot,
736            Rectangle2D dataArea) {
737        plot.drawOutline(g2, dataArea);
738    }
739
740    /**
741     * Draws a grid line against the domain axis.
742     * <P>
743     * Note that this default implementation assumes that the horizontal axis
744     * is the domain axis. If this is not the case, you will need to override
745     * this method.
746     *
747     * @param g2  the graphics device.
748     * @param plot  the plot.
749     * @param dataArea  the area for plotting data.
750     * @param value  the Java2D value at which the grid line should be drawn.
751     *
752     */
753    @Override
754    public void drawDomainGridline(Graphics2D g2, CategoryPlot plot,
755           Rectangle2D dataArea, double value) {
756
757        Line2D line = null;
758        PlotOrientation orientation = plot.getOrientation();
759
760        if (orientation == PlotOrientation.HORIZONTAL) {
761            line = new Line2D.Double(dataArea.getMinX(), value,
762                    dataArea.getMaxX(), value);
763        }
764        else if (orientation == PlotOrientation.VERTICAL) {
765            line = new Line2D.Double(value, dataArea.getMinY(), value,
766                    dataArea.getMaxY());
767        }
768
769        Paint paint = plot.getDomainGridlinePaint();
770        if (paint == null) {
771            paint = CategoryPlot.DEFAULT_GRIDLINE_PAINT;
772        }
773        g2.setPaint(paint);
774
775        Stroke stroke = plot.getDomainGridlineStroke();
776        if (stroke == null) {
777            stroke = CategoryPlot.DEFAULT_GRIDLINE_STROKE;
778        }
779        g2.setStroke(stroke);
780        Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
781        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
782                RenderingHints.VALUE_STROKE_NORMALIZE);
783        g2.draw(line);
784        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved);
785    }
786
787    /**
788     * Draws a line perpendicular to the range axis.
789     *
790     * @param g2  the graphics device.
791     * @param plot  the plot.
792     * @param axis  the value axis.
793     * @param dataArea  the area for plotting data.
794     * @param value  the value at which the grid line should be drawn.
795     * @param paint  the paint ({@code null} not permitted).
796     * @param stroke  the stroke ({@code null} not permitted).
797     */
798    @Override
799    public void drawRangeLine(Graphics2D g2, CategoryPlot plot, ValueAxis axis,
800            Rectangle2D dataArea, double value, Paint paint, Stroke stroke) {
801
802        Range range = axis.getRange();
803        if (!range.contains(value)) {
804            return;
805        }
806
807        PlotOrientation orientation = plot.getOrientation();
808        Line2D line = null;
809        double v = axis.valueToJava2D(value, dataArea, plot.getRangeAxisEdge());
810        if (orientation == PlotOrientation.HORIZONTAL) {
811            line = new Line2D.Double(v, dataArea.getMinY(), v,
812                    dataArea.getMaxY());
813        } else if (orientation == PlotOrientation.VERTICAL) {
814            line = new Line2D.Double(dataArea.getMinX(), v,
815                    dataArea.getMaxX(), v);
816        }
817
818        g2.setPaint(paint);
819        g2.setStroke(stroke);
820        Object saved = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
821        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, 
822                RenderingHints.VALUE_STROKE_NORMALIZE);
823        g2.draw(line);
824        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, saved);
825    }
826
827    /**
828     * Draws a marker for the domain axis.
829     *
830     * @param g2  the graphics device (not {@code null}).
831     * @param plot  the plot (not {@code null}).
832     * @param axis  the range axis (not {@code null}).
833     * @param marker  the marker to be drawn (not {@code null}).
834     * @param dataArea  the area inside the axes (not {@code null}).
835     *
836     * @see #drawRangeMarker(Graphics2D, CategoryPlot, ValueAxis, Marker,
837     *     Rectangle2D)
838     */
839    @Override
840    public void drawDomainMarker(Graphics2D g2, CategoryPlot plot,
841            CategoryAxis axis, CategoryMarker marker, Rectangle2D dataArea) {
842
843        Comparable category = marker.getKey();
844        CategoryDataset dataset = plot.getDataset(plot.getIndexOf(this));
845        int columnIndex = dataset.getColumnIndex(category);
846        if (columnIndex < 0) {
847            return;
848        }
849
850        final Composite savedComposite = g2.getComposite();
851        g2.setComposite(AlphaComposite.getInstance(
852                AlphaComposite.SRC_OVER, marker.getAlpha()));
853
854        PlotOrientation orientation = plot.getOrientation();
855        Rectangle2D bounds;
856        if (marker.getDrawAsLine()) {
857            double v = axis.getCategoryMiddle(columnIndex,
858                    dataset.getColumnCount(), dataArea,
859                    plot.getDomainAxisEdge());
860            Line2D line = null;
861            if (orientation == PlotOrientation.HORIZONTAL) {
862                line = new Line2D.Double(dataArea.getMinX(), v,
863                        dataArea.getMaxX(), v);
864            }
865            else if (orientation == PlotOrientation.VERTICAL) {
866                line = new Line2D.Double(v, dataArea.getMinY(), v,
867                        dataArea.getMaxY());
868            } else {
869                throw new IllegalStateException();
870            }
871            g2.setPaint(marker.getPaint());
872            g2.setStroke(marker.getStroke());
873            g2.draw(line);
874            bounds = line.getBounds2D();
875        }
876        else {
877            double v0 = axis.getCategoryStart(columnIndex,
878                    dataset.getColumnCount(), dataArea,
879                    plot.getDomainAxisEdge());
880            double v1 = axis.getCategoryEnd(columnIndex,
881                    dataset.getColumnCount(), dataArea,
882                    plot.getDomainAxisEdge());
883            Rectangle2D area = null;
884            if (orientation == PlotOrientation.HORIZONTAL) {
885                area = new Rectangle2D.Double(dataArea.getMinX(), v0,
886                        dataArea.getWidth(), (v1 - v0));
887            }
888            else if (orientation == PlotOrientation.VERTICAL) {
889                area = new Rectangle2D.Double(v0, dataArea.getMinY(),
890                        (v1 - v0), dataArea.getHeight());
891            }
892            g2.setPaint(marker.getPaint());
893            g2.fill(area);
894            bounds = area;
895        }
896
897        String label = marker.getLabel();
898        RectangleAnchor anchor = marker.getLabelAnchor();
899        if (label != null) {
900            Font labelFont = marker.getLabelFont();
901            g2.setFont(labelFont);
902            g2.setPaint(marker.getLabelPaint());
903            Point2D coordinates = calculateDomainMarkerTextAnchorPoint(
904                    g2, orientation, dataArea, bounds, marker.getLabelOffset(),
905                    marker.getLabelOffsetType(), anchor);
906            TextUtils.drawAlignedString(label, g2,
907                    (float) coordinates.getX(), (float) coordinates.getY(),
908                    marker.getLabelTextAnchor());
909        }
910        g2.setComposite(savedComposite);
911    }
912
913    /**
914     * Draws a marker for the range axis.
915     *
916     * @param g2  the graphics device (not {@code null}).
917     * @param plot  the plot (not {@code null}).
918     * @param axis  the range axis (not {@code null}).
919     * @param marker  the marker to be drawn (not {@code null}).
920     * @param dataArea  the area inside the axes (not {@code null}).
921     *
922     * @see #drawDomainMarker(Graphics2D, CategoryPlot, CategoryAxis,
923     *     CategoryMarker, Rectangle2D)
924     */
925    @Override
926    public void drawRangeMarker(Graphics2D g2, CategoryPlot plot,
927            ValueAxis axis, Marker marker, Rectangle2D dataArea) {
928
929        if (marker instanceof ValueMarker) {
930            ValueMarker vm = (ValueMarker) marker;
931            double value = vm.getValue();
932            Range range = axis.getRange();
933
934            if (!range.contains(value)) {
935                return;
936            }
937
938            final Composite savedComposite = g2.getComposite();
939            g2.setComposite(AlphaComposite.getInstance(
940                    AlphaComposite.SRC_OVER, marker.getAlpha()));
941
942            PlotOrientation orientation = plot.getOrientation();
943            double v = axis.valueToJava2D(value, dataArea,
944                    plot.getRangeAxisEdge());
945            Line2D line = null;
946            if (orientation == PlotOrientation.HORIZONTAL) {
947                line = new Line2D.Double(v, dataArea.getMinY(), v,
948                        dataArea.getMaxY());
949            }
950            else if (orientation == PlotOrientation.VERTICAL) {
951                line = new Line2D.Double(dataArea.getMinX(), v,
952                        dataArea.getMaxX(), v);
953            } else {
954                throw new IllegalStateException();
955            }
956
957            g2.setPaint(marker.getPaint());
958            g2.setStroke(marker.getStroke());
959            g2.draw(line);
960
961            String label = marker.getLabel();
962            RectangleAnchor anchor = marker.getLabelAnchor();
963            if (label != null) {
964                Font labelFont = marker.getLabelFont();
965                g2.setFont(labelFont);
966                Point2D coordinates = calculateRangeMarkerTextAnchorPoint(
967                        g2, orientation, dataArea, line.getBounds2D(),
968                        marker.getLabelOffset(), LengthAdjustmentType.EXPAND,
969                        anchor);
970                Rectangle2D rect = TextUtils.calcAlignedStringBounds(label, g2, 
971                        (float) coordinates.getX(), (float) coordinates.getY(), 
972                        marker.getLabelTextAnchor());
973                g2.setPaint(marker.getLabelBackgroundColor());
974                g2.fill(rect);
975                g2.setPaint(marker.getLabelPaint());
976                TextUtils.drawAlignedString(label, g2, 
977                        (float) coordinates.getX(), (float) coordinates.getY(),
978                        marker.getLabelTextAnchor());
979            }
980            g2.setComposite(savedComposite);
981        }
982        else if (marker instanceof IntervalMarker) {
983            IntervalMarker im = (IntervalMarker) marker;
984            double start = im.getStartValue();
985            double end = im.getEndValue();
986            Range range = axis.getRange();
987            if (!(range.intersects(start, end))) {
988                return;
989            }
990
991            final Composite savedComposite = g2.getComposite();
992            g2.setComposite(AlphaComposite.getInstance(
993                    AlphaComposite.SRC_OVER, marker.getAlpha()));
994
995            double start2d = axis.valueToJava2D(start, dataArea,
996                    plot.getRangeAxisEdge());
997            double end2d = axis.valueToJava2D(end, dataArea,
998                    plot.getRangeAxisEdge());
999            double low = Math.min(start2d, end2d);
1000            double high = Math.max(start2d, end2d);
1001
1002            PlotOrientation orientation = plot.getOrientation();
1003            Rectangle2D rect = null;
1004            if (orientation == PlotOrientation.HORIZONTAL) {
1005                // clip left and right bounds to data area
1006                low = Math.max(low, dataArea.getMinX());
1007                high = Math.min(high, dataArea.getMaxX());
1008                rect = new Rectangle2D.Double(low,
1009                        dataArea.getMinY(), high - low,
1010                        dataArea.getHeight());
1011            }
1012            else if (orientation == PlotOrientation.VERTICAL) {
1013                // clip top and bottom bounds to data area
1014                low = Math.max(low, dataArea.getMinY());
1015                high = Math.min(high, dataArea.getMaxY());
1016                rect = new Rectangle2D.Double(dataArea.getMinX(),
1017                        low, dataArea.getWidth(),
1018                        high - low);
1019            }
1020            Paint p = marker.getPaint();
1021            if (p instanceof GradientPaint) {
1022                GradientPaint gp = (GradientPaint) p;
1023                GradientPaintTransformer t = im.getGradientPaintTransformer();
1024                if (t != null) {
1025                    gp = t.transform(gp, rect);
1026                }
1027                g2.setPaint(gp);
1028            }
1029            else {
1030                g2.setPaint(p);
1031            }
1032            g2.fill(rect);
1033
1034            // now draw the outlines, if visible...
1035            if (im.getOutlinePaint() != null && im.getOutlineStroke() != null) {
1036                if (orientation == PlotOrientation.VERTICAL) {
1037                    Line2D line = new Line2D.Double();
1038                    double x0 = dataArea.getMinX();
1039                    double x1 = dataArea.getMaxX();
1040                    g2.setPaint(im.getOutlinePaint());
1041                    g2.setStroke(im.getOutlineStroke());
1042                    if (range.contains(start)) {
1043                        line.setLine(x0, start2d, x1, start2d);
1044                        g2.draw(line);
1045                    }
1046                    if (range.contains(end)) {
1047                        line.setLine(x0, end2d, x1, end2d);
1048                        g2.draw(line);
1049                    }
1050                } else { // PlotOrientation.HORIZONTAL
1051                    Line2D line = new Line2D.Double();
1052                    double y0 = dataArea.getMinY();
1053                    double y1 = dataArea.getMaxY();
1054                    g2.setPaint(im.getOutlinePaint());
1055                    g2.setStroke(im.getOutlineStroke());
1056                    if (range.contains(start)) {
1057                        line.setLine(start2d, y0, start2d, y1);
1058                        g2.draw(line);
1059                    }
1060                    if (range.contains(end)) {
1061                        line.setLine(end2d, y0, end2d, y1);
1062                        g2.draw(line);
1063                    }
1064                }
1065            }
1066
1067            String label = marker.getLabel();
1068            RectangleAnchor anchor = marker.getLabelAnchor();
1069            if (label != null) {
1070                Font labelFont = marker.getLabelFont();
1071                g2.setFont(labelFont);
1072                Point2D coords = calculateRangeMarkerTextAnchorPoint(
1073                        g2, orientation, dataArea, rect,
1074                        marker.getLabelOffset(), marker.getLabelOffsetType(),
1075                        anchor);
1076                Rectangle2D r = TextUtils.calcAlignedStringBounds(label, 
1077                        g2, (float) coords.getX(), (float) coords.getY(), 
1078                        marker.getLabelTextAnchor());
1079                g2.setPaint(marker.getLabelBackgroundColor());
1080                g2.fill(r);
1081                g2.setPaint(marker.getLabelPaint());
1082                TextUtils.drawAlignedString(label, g2,
1083                        (float) coords.getX(), (float) coords.getY(),
1084                        marker.getLabelTextAnchor());
1085            }
1086            g2.setComposite(savedComposite);
1087        }
1088    }
1089
1090    /**
1091     * Calculates the {@code (x, y)} coordinates for drawing the label for a 
1092     * marker on the range axis.
1093     *
1094     * @param g2  the graphics device.
1095     * @param orientation  the plot orientation.
1096     * @param dataArea  the data area.
1097     * @param markerArea  the rectangle surrounding the marker.
1098     * @param markerOffset  the marker offset.
1099     * @param labelOffsetType  the label offset type.
1100     * @param anchor  the label anchor.
1101     *
1102     * @return The coordinates for drawing the marker label.
1103     */
1104    protected Point2D calculateDomainMarkerTextAnchorPoint(Graphics2D g2,
1105            PlotOrientation orientation, Rectangle2D dataArea,
1106            Rectangle2D markerArea, RectangleInsets markerOffset,
1107            LengthAdjustmentType labelOffsetType, RectangleAnchor anchor) {
1108
1109        Rectangle2D anchorRect = null;
1110        if (orientation == PlotOrientation.HORIZONTAL) {
1111            anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1112                    LengthAdjustmentType.CONTRACT, labelOffsetType);
1113        } else if (orientation == PlotOrientation.VERTICAL) {
1114            anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1115                    labelOffsetType, LengthAdjustmentType.CONTRACT);
1116        }
1117        return anchor.getAnchorPoint(anchorRect);
1118    }
1119
1120    /**
1121     * Calculates the (x, y) coordinates for drawing a marker label.
1122     *
1123     * @param g2  the graphics device.
1124     * @param orientation  the plot orientation.
1125     * @param dataArea  the data area.
1126     * @param markerArea  the rectangle surrounding the marker.
1127     * @param markerOffset  the marker offset.
1128     * @param labelOffsetType  the label offset type.
1129     * @param anchor  the label anchor.
1130     *
1131     * @return The coordinates for drawing the marker label.
1132     */
1133    protected Point2D calculateRangeMarkerTextAnchorPoint(Graphics2D g2,
1134            PlotOrientation orientation, Rectangle2D dataArea,
1135            Rectangle2D markerArea, RectangleInsets markerOffset,
1136            LengthAdjustmentType labelOffsetType, RectangleAnchor anchor) {
1137
1138        Rectangle2D anchorRect = null;
1139        if (orientation == PlotOrientation.HORIZONTAL) {
1140            anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1141                    labelOffsetType, LengthAdjustmentType.CONTRACT);
1142        } else if (orientation == PlotOrientation.VERTICAL) {
1143            anchorRect = markerOffset.createAdjustedRectangle(markerArea,
1144                    LengthAdjustmentType.CONTRACT, labelOffsetType);
1145        }
1146        return anchor.getAnchorPoint(anchorRect);
1147
1148    }
1149
1150    /**
1151     * Returns a legend item for a series.  This default implementation will
1152     * return {@code null} if {@link #isSeriesVisible(int)} or
1153     * {@link #isSeriesVisibleInLegend(int)} returns {@code false}.
1154     *
1155     * @param datasetIndex  the dataset index (zero-based).
1156     * @param series  the series index (zero-based).
1157     *
1158     * @return The legend item (possibly {@code null}).
1159     *
1160     * @see #getLegendItems()
1161     */
1162    @Override
1163    public LegendItem getLegendItem(int datasetIndex, int series) {
1164
1165        CategoryPlot p = getPlot();
1166        if (p == null) {
1167            return null;
1168        }
1169
1170        // check that a legend item needs to be displayed...
1171        if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
1172            return null;
1173        }
1174
1175        CategoryDataset dataset = p.getDataset(datasetIndex);
1176        String label = this.legendItemLabelGenerator.generateLabel(dataset,
1177                series);
1178        String description = label;
1179        String toolTipText = null;
1180        if (this.legendItemToolTipGenerator != null) {
1181            toolTipText = this.legendItemToolTipGenerator.generateLabel(
1182                    dataset, series);
1183        }
1184        String urlText = null;
1185        if (this.legendItemURLGenerator != null) {
1186            urlText = this.legendItemURLGenerator.generateLabel(dataset,
1187                    series);
1188        }
1189        Shape shape = lookupLegendShape(series);
1190        Paint paint = lookupSeriesPaint(series);
1191        Paint outlinePaint = lookupSeriesOutlinePaint(series);
1192        Stroke outlineStroke = lookupSeriesOutlineStroke(series);
1193
1194        LegendItem item = new LegendItem(label, description, toolTipText,
1195                urlText, shape, paint, outlineStroke, outlinePaint);
1196        item.setLabelFont(lookupLegendTextFont(series));
1197        Paint labelPaint = lookupLegendTextPaint(series);
1198        if (labelPaint != null) {
1199            item.setLabelPaint(labelPaint);
1200        }
1201        item.setSeriesKey(dataset.getRowKey(series));
1202        item.setSeriesIndex(series);
1203        item.setDataset(dataset);
1204        item.setDatasetIndex(datasetIndex);
1205        return item;
1206    }
1207
1208    /**
1209     * Tests this renderer for equality with another object.
1210     *
1211     * @param obj  the object.
1212     *
1213     * @return {@code true} or {@code false}.
1214     */
1215    @Override
1216    public boolean equals(Object obj) {
1217        if (obj == this) {
1218            return true;
1219        }
1220        if (!(obj instanceof AbstractCategoryItemRenderer)) {
1221            return false;
1222        }
1223        AbstractCategoryItemRenderer that = (AbstractCategoryItemRenderer) obj;
1224
1225        if (!Objects.equals(this.itemLabelGeneratorMap,
1226                that.itemLabelGeneratorMap)) {
1227            return false;
1228        }
1229        if (!Objects.equals(this.defaultItemLabelGenerator,
1230                that.defaultItemLabelGenerator)) {
1231            return false;
1232        }
1233        if (!Objects.equals(this.toolTipGeneratorMap,
1234                that.toolTipGeneratorMap)) {
1235            return false;
1236        }
1237        if (!Objects.equals(this.defaultToolTipGenerator,
1238                that.defaultToolTipGenerator)) {
1239            return false;
1240        }
1241        if (!Objects.equals(this.itemURLGeneratorMap,
1242                that.itemURLGeneratorMap)) {
1243            return false;
1244        }
1245        if (!Objects.equals(this.defaultItemURLGenerator,
1246                that.defaultItemURLGenerator)) {
1247            return false;
1248        }
1249        if (!Objects.equals(this.legendItemLabelGenerator,
1250                that.legendItemLabelGenerator)) {
1251            return false;
1252        }
1253        if (!Objects.equals(this.legendItemToolTipGenerator,
1254                that.legendItemToolTipGenerator)) {
1255            return false;
1256        }
1257        if (!Objects.equals(this.legendItemURLGenerator,
1258                that.legendItemURLGenerator)) {
1259            return false;
1260        }
1261        return super.equals(obj);
1262    }
1263
1264    /**
1265     * Returns a hash code for the renderer.
1266     *
1267     * @return The hash code.
1268     */
1269    @Override
1270    public int hashCode() {
1271        int result = super.hashCode();
1272        return result;
1273    }
1274
1275    /**
1276     * Returns the drawing supplier from the plot.
1277     *
1278     * @return The drawing supplier (possibly {@code null}).
1279     */
1280    @Override
1281    public DrawingSupplier getDrawingSupplier() {
1282        DrawingSupplier result = null;
1283        CategoryPlot cp = getPlot();
1284        if (cp != null) {
1285            result = cp.getDrawingSupplier();
1286        }
1287        return result;
1288    }
1289
1290    /**
1291     * Considers the current (x, y) coordinate and updates the crosshair point
1292     * if it meets the criteria (usually means the (x, y) coordinate is the
1293     * closest to the anchor point so far).
1294     *
1295     * @param crosshairState  the crosshair state ({@code null} permitted,
1296     *                        but the method does nothing in that case).
1297     * @param rowKey  the row key.
1298     * @param columnKey  the column key.
1299     * @param value  the data value.
1300     * @param datasetIndex  the dataset index.
1301     * @param transX  the x-value translated to Java2D space.
1302     * @param transY  the y-value translated to Java2D space.
1303     * @param orientation  the plot orientation ({@code null} not permitted).
1304     */
1305    protected void updateCrosshairValues(CategoryCrosshairState crosshairState,
1306            Comparable rowKey, Comparable columnKey, double value,
1307            int datasetIndex,
1308            double transX, double transY, PlotOrientation orientation) {
1309
1310        Args.nullNotPermitted(orientation, "orientation");
1311
1312        if (crosshairState != null) {
1313            if (this.plot.isRangeCrosshairLockedOnData()) {
1314                // both axes
1315                crosshairState.updateCrosshairPoint(rowKey, columnKey, value,
1316                        datasetIndex, transX, transY, orientation);
1317            }
1318            else {
1319                crosshairState.updateCrosshairX(rowKey, columnKey,
1320                        datasetIndex, transX, orientation);
1321            }
1322        }
1323    }
1324
1325    /**
1326     * Draws an item label.
1327     *
1328     * @param g2  the graphics device.
1329     * @param orientation  the orientation.
1330     * @param dataset  the dataset.
1331     * @param row  the row.
1332     * @param column  the column.
1333     * @param x  the x coordinate (in Java2D space).
1334     * @param y  the y coordinate (in Java2D space).
1335     * @param negative  indicates a negative value (which affects the item
1336     *                  label position).
1337     */
1338    protected void drawItemLabel(Graphics2D g2, PlotOrientation orientation,
1339            CategoryDataset dataset, int row, int column,
1340            double x, double y, boolean negative) {
1341
1342        CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
1343                column);
1344        if (generator != null) {
1345            Font labelFont = getItemLabelFont(row, column);
1346            Paint paint = getItemLabelPaint(row, column);
1347            g2.setFont(labelFont);
1348            g2.setPaint(paint);
1349            String label = generator.generateLabel(dataset, row, column);
1350            ItemLabelPosition position;
1351            if (!negative) {
1352                position = getPositiveItemLabelPosition(row, column);
1353            }
1354            else {
1355                position = getNegativeItemLabelPosition(row, column);
1356            }
1357            Point2D anchorPoint = calculateLabelAnchorPoint(
1358                    position.getItemLabelAnchor(), x, y, orientation);
1359            TextUtils.drawRotatedString(label, g2,
1360                    (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1361                    position.getTextAnchor(),
1362                    position.getAngle(), position.getRotationAnchor());
1363        }
1364
1365    }
1366
1367    /**
1368     * Returns an independent copy of the renderer.  The {@code plot}
1369     * reference is shallow copied.
1370     *
1371     * @return A clone.
1372     *
1373     * @throws CloneNotSupportedException  can be thrown if one of the objects
1374     *         belonging to the renderer does not support cloning (for example,
1375     *         an item label generator).
1376     */
1377    @Override
1378    public Object clone() throws CloneNotSupportedException {
1379        AbstractCategoryItemRenderer clone
1380            = (AbstractCategoryItemRenderer) super.clone();
1381
1382        if (this.itemLabelGeneratorMap != null) {
1383            clone.itemLabelGeneratorMap = CloneUtils.cloneMapValues(
1384                    this.itemLabelGeneratorMap);
1385        }
1386
1387        if (this.defaultItemLabelGenerator != null) {
1388            if (this.defaultItemLabelGenerator instanceof PublicCloneable) {
1389                PublicCloneable pc
1390                        = (PublicCloneable) this.defaultItemLabelGenerator;
1391                clone.defaultItemLabelGenerator
1392                        = (CategoryItemLabelGenerator) pc.clone();
1393            }
1394            else {
1395                throw new CloneNotSupportedException(
1396                        "ItemLabelGenerator not cloneable.");
1397            }
1398        }
1399
1400        if (this.toolTipGeneratorMap != null) {
1401            clone.toolTipGeneratorMap = CloneUtils.cloneMapValues(
1402                    this.toolTipGeneratorMap);
1403        }
1404
1405        if (this.defaultToolTipGenerator != null) {
1406            if (this.defaultToolTipGenerator instanceof PublicCloneable) {
1407                PublicCloneable pc
1408                        = (PublicCloneable) this.defaultToolTipGenerator;
1409                clone.defaultToolTipGenerator
1410                        = (CategoryToolTipGenerator) pc.clone();
1411            }
1412            else {
1413                throw new CloneNotSupportedException(
1414                        "Default tool tip generator not cloneable.");
1415            }
1416        }
1417
1418        if (this.itemURLGeneratorMap != null) {
1419            clone.itemURLGeneratorMap = CloneUtils.cloneMapValues(
1420                    this.itemURLGeneratorMap);
1421        }
1422
1423        if (this.defaultItemURLGenerator != null) {
1424            if (this.defaultItemURLGenerator instanceof PublicCloneable) {
1425                PublicCloneable pc
1426                        = (PublicCloneable) this.defaultItemURLGenerator;
1427                clone.defaultItemURLGenerator = (CategoryURLGenerator) pc.clone();
1428            }
1429            else {
1430                throw new CloneNotSupportedException(
1431                        "Default item URL generator not cloneable.");
1432            }
1433        }
1434
1435        if (this.legendItemLabelGenerator instanceof PublicCloneable) {
1436            clone.legendItemLabelGenerator = (CategorySeriesLabelGenerator)
1437                    ObjectUtils.clone(this.legendItemLabelGenerator);
1438        }
1439        if (this.legendItemToolTipGenerator instanceof PublicCloneable) {
1440            clone.legendItemToolTipGenerator = (CategorySeriesLabelGenerator)
1441                    ObjectUtils.clone(this.legendItemToolTipGenerator);
1442        }
1443        if (this.legendItemURLGenerator instanceof PublicCloneable) {
1444            clone.legendItemURLGenerator = (CategorySeriesLabelGenerator)
1445                    ObjectUtils.clone(this.legendItemURLGenerator);
1446        }
1447        return clone;
1448    }
1449
1450    /**
1451     * Returns a domain axis for a plot.
1452     *
1453     * @param plot  the plot.
1454     * @param index  the axis index.
1455     *
1456     * @return A domain axis.
1457     */
1458    protected CategoryAxis getDomainAxis(CategoryPlot plot, int index) {
1459        CategoryAxis result = plot.getDomainAxis(index);
1460        if (result == null) {
1461            result = plot.getDomainAxis();
1462        }
1463        return result;
1464    }
1465
1466    /**
1467     * Returns a range axis for a plot.
1468     *
1469     * @param plot  the plot.
1470     * @param index  the axis index.
1471     *
1472     * @return A range axis.
1473     */
1474    protected ValueAxis getRangeAxis(CategoryPlot plot, int index) {
1475        ValueAxis result = plot.getRangeAxis(index);
1476        if (result == null) {
1477            result = plot.getRangeAxis();
1478        }
1479        return result;
1480    }
1481
1482    /**
1483     * Returns a (possibly empty) collection of legend items for the series
1484     * that this renderer is responsible for drawing.
1485     *
1486     * @return The legend item collection (never {@code null}).
1487     *
1488     * @see #getLegendItem(int, int)
1489     */
1490    @Override
1491    public LegendItemCollection getLegendItems() {
1492        LegendItemCollection result = new LegendItemCollection();
1493        if (this.plot == null) {
1494            return result;
1495        }
1496        int index = this.plot.getIndexOf(this);
1497        CategoryDataset dataset = this.plot.getDataset(index);
1498        if (dataset == null) {
1499            return result;
1500        }
1501        int seriesCount = dataset.getRowCount();
1502        if (plot.getRowRenderingOrder().equals(SortOrder.ASCENDING)) {
1503            for (int i = 0; i < seriesCount; i++) {
1504                if (isSeriesVisibleInLegend(i)) {
1505                    LegendItem item = getLegendItem(index, i);
1506                    if (item != null) {
1507                        result.add(item);
1508                    }
1509                }
1510            }
1511        }
1512        else {
1513            for (int i = seriesCount - 1; i >= 0; i--) {
1514                if (isSeriesVisibleInLegend(i)) {
1515                    LegendItem item = getLegendItem(index, i);
1516                    if (item != null) {
1517                        result.add(item);
1518                    }
1519                }
1520            }
1521        }
1522        return result;
1523    }
1524
1525    /**
1526     * Returns the legend item label generator.
1527     *
1528     * @return The label generator (never {@code null}).
1529     *
1530     * @see #setLegendItemLabelGenerator(CategorySeriesLabelGenerator)
1531     */
1532    public CategorySeriesLabelGenerator getLegendItemLabelGenerator() {
1533        return this.legendItemLabelGenerator;
1534    }
1535
1536    /**
1537     * Sets the legend item label generator and sends a
1538     * {@link RendererChangeEvent} to all registered listeners.
1539     *
1540     * @param generator  the generator ({@code null} not permitted).
1541     *
1542     * @see #getLegendItemLabelGenerator()
1543     */
1544    public void setLegendItemLabelGenerator(
1545            CategorySeriesLabelGenerator generator) {
1546        Args.nullNotPermitted(generator, "generator");
1547        this.legendItemLabelGenerator = generator;
1548        fireChangeEvent();
1549    }
1550
1551    /**
1552     * Returns the legend item tool tip generator.
1553     *
1554     * @return The tool tip generator (possibly {@code null}).
1555     *
1556     * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator)
1557     */
1558    public CategorySeriesLabelGenerator getLegendItemToolTipGenerator() {
1559        return this.legendItemToolTipGenerator;
1560    }
1561
1562    /**
1563     * Sets the legend item tool tip generator and sends a
1564     * {@link RendererChangeEvent} to all registered listeners.
1565     *
1566     * @param generator  the generator ({@code null} permitted).
1567     *
1568     * @see #setLegendItemToolTipGenerator(CategorySeriesLabelGenerator)
1569     */
1570    public void setLegendItemToolTipGenerator(
1571            CategorySeriesLabelGenerator generator) {
1572        this.legendItemToolTipGenerator = generator;
1573        fireChangeEvent();
1574    }
1575
1576    /**
1577     * Returns the legend item URL generator.
1578     *
1579     * @return The URL generator (possibly {@code null}).
1580     *
1581     * @see #setLegendItemURLGenerator(CategorySeriesLabelGenerator)
1582     */
1583    public CategorySeriesLabelGenerator getLegendItemURLGenerator() {
1584        return this.legendItemURLGenerator;
1585    }
1586
1587    /**
1588     * Sets the legend item URL generator and sends a
1589     * {@link RendererChangeEvent} to all registered listeners.
1590     *
1591     * @param generator  the generator ({@code null} permitted).
1592     *
1593     * @see #getLegendItemURLGenerator()
1594     */
1595    public void setLegendItemURLGenerator(
1596            CategorySeriesLabelGenerator generator) {
1597        this.legendItemURLGenerator = generator;
1598        fireChangeEvent();
1599    }
1600    
1601    /**
1602     * Adds an entity with the specified hotspot.
1603     *
1604     * @param entities  the entity collection.
1605     * @param dataset  the dataset.
1606     * @param row  the row index.
1607     * @param column  the column index.
1608     * @param hotspot  the hotspot ({@code null} not permitted).
1609     */
1610    protected void addItemEntity(EntityCollection entities,
1611            CategoryDataset dataset, int row, int column, Shape hotspot) {
1612        Args.nullNotPermitted(hotspot, "hotspot");
1613        if (!getItemCreateEntity(row, column)) {
1614            return;
1615        }
1616        String tip = null;
1617        CategoryToolTipGenerator tipster = getToolTipGenerator(row, column);
1618        if (tipster != null) {
1619            tip = tipster.generateToolTip(dataset, row, column);
1620        }
1621        String url = null;
1622        CategoryURLGenerator urlster = getItemURLGenerator(row, column);
1623        if (urlster != null) {
1624            url = urlster.generateURL(dataset, row, column);
1625        }
1626        CategoryItemEntity entity = new CategoryItemEntity(hotspot, tip, url,
1627                dataset, dataset.getRowKey(row), dataset.getColumnKey(column));
1628        entities.add(entity);
1629    }
1630
1631    /**
1632     * Adds an entity to the collection.
1633     *
1634     * @param entities  the entity collection being populated.
1635     * @param hotspot  the entity area (if {@code null} a default will be
1636     *              used).
1637     * @param dataset  the dataset.
1638     * @param row  the series.
1639     * @param column  the item.
1640     * @param entityX  the entity's center x-coordinate in user space (only
1641     *                 used if {@code area} is {@code null}).
1642     * @param entityY  the entity's center y-coordinate in user space (only
1643     *                 used if {@code area} is {@code null}).
1644     */
1645    protected void addEntity(EntityCollection entities, Shape hotspot,
1646                             CategoryDataset dataset, int row, int column,
1647                             double entityX, double entityY) {
1648        if (!getItemCreateEntity(row, column)) {
1649            return;
1650        }
1651        Shape s = hotspot;
1652        if (hotspot == null) {
1653            double r = getDefaultEntityRadius();
1654            double w = r * 2;
1655            if (getPlot().getOrientation() == PlotOrientation.VERTICAL) {
1656                s = new Ellipse2D.Double(entityX - r, entityY - r, w, w);
1657            }
1658            else {
1659                s = new Ellipse2D.Double(entityY - r, entityX - r, w, w);
1660            }
1661        }
1662        String tip = null;
1663        CategoryToolTipGenerator generator = getToolTipGenerator(row, column);
1664        if (generator != null) {
1665            tip = generator.generateToolTip(dataset, row, column);
1666        }
1667        String url = null;
1668        CategoryURLGenerator urlster = getItemURLGenerator(row, column);
1669        if (urlster != null) {
1670            url = urlster.generateURL(dataset, row, column);
1671        }
1672        CategoryItemEntity entity = new CategoryItemEntity(s, tip, url,
1673                dataset, dataset.getRowKey(row), dataset.getColumnKey(column));
1674        entities.add(entity);
1675    }
1676
1677}