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