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