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