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