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