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