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