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