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