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