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