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