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