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