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