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