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