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