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