    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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     * XYPlot.java
    029     * -----------
    030     * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Craig MacFarlane;
    034     *                   Mark Watson (www.markwatson.com);
    035     *                   Jonathan Nash;
    036     *                   Gideon Krause;
    037     *                   Klaus Rheinwald;
    038     *                   Xavier Poinsard;
    039     *                   Richard Atkinson;
    040     *                   Arnaud Lelievre;
    041     *                   Nicolas Brodu;
    042     *                   Eduardo Ramalho;
    043     *                   Sergei Ivanov;
    044     *                   Richard West, Advanced Micro Devices, Inc.;
    045     *
    046     * Changes (from 21-Jun-2001)
    047     * --------------------------
    048     * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
    049     * 18-Sep-2001 : Updated header and fixed DOS encoding problem (DG);
    050     * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG);
    051     * 19-Oct-2001 : Removed the code for drawing the visual representation of each
    052     *               data point into a separate class StandardXYItemRenderer.
    053     *               This will make it easier to add variations to the way the
    054     *               charts are drawn.  Based on code contributed by Mark
    055     *               Watson (DG);
    056     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
    057     * 20-Nov-2001 : Fixed clipping bug that shows up when chart is displayed
    058     *               inside JScrollPane (DG);
    059     * 12-Dec-2001 : Removed unnecessary 'throws' clauses from constructor (DG);
    060     * 13-Dec-2001 : Added skeleton code for tooltips.  Added new constructor. (DG);
    061     * 16-Jan-2002 : Renamed the tooltips class (DG);
    062     * 22-Jan-2002 : Added DrawInfo class, incorporating tooltips and crosshairs.
    063     *               Crosshairs based on code by Jonathan Nash (DG);
    064     * 05-Feb-2002 : Added alpha-transparency setting based on code by Sylvain
    065     *               Vieujot (DG);
    066     * 26-Feb-2002 : Updated getMinimumXXX() and getMaximumXXX() methods to handle
    067     *               special case when chart is null (DG);
    068     * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
    069     * 28-Mar-2002 : The plot now registers with the renderer as a property change
    070     *               listener.  Also added a new constructor (DG);
    071     * 09-Apr-2002 : Removed the transRangeZero from the renderer.drawItem()
    072     *               method.  Moved the tooltip generator into the renderer (DG);
    073     * 23-Apr-2002 : Fixed bug in methods for drawing horizontal and vertical
    074     *               lines (DG);
    075     * 13-May-2002 : Small change to the draw() method so that it works for
    076     *               OverlaidXYPlot also (DG);
    077     * 25-Jun-2002 : Removed redundant import (DG);
    078     * 20-Aug-2002 : Renamed getItemRenderer() --> getRenderer(), and
    079     *               setXYItemRenderer() --> setRenderer() (DG);
    080     * 28-Aug-2002 : Added mechanism for (optional) plot annotations (DG);
    081     * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    082     * 18-Nov-2002 : Added grid settings for both domain and range axis (previously
    083     *               these were set in the axes) (DG);
    084     * 09-Jan-2003 : Further additions to the grid settings, plus integrated plot
    085     *               border bug fix contributed by Gideon Krause (DG);
    086     * 22-Jan-2003 : Removed monolithic constructor (DG);
    087     * 04-Mar-2003 : Added 'no data' message, see bug report 691634.  Added
    088     *               secondary range markers using code contributed by Klaus
    089     *               Rheinwald (DG);
    090     * 26-Mar-2003 : Implemented Serializable (DG);
    091     * 03-Apr-2003 : Added setDomainAxisLocation() method (DG);
    092     * 30-Apr-2003 : Moved annotation drawing into a separate method (DG);
    093     * 01-May-2003 : Added multi-pass mechanism for renderers (DG);
    094     * 02-May-2003 : Changed axis locations from int to AxisLocation (DG);
    095     * 15-May-2003 : Added an orientation attribute (DG);
    096     * 02-Jun-2003 : Removed range axis compatibility test (DG);
    097     * 05-Jun-2003 : Added domain and range grid bands (sponsored by Focus Computer
    098     *               Services Ltd) (DG);
    099     * 26-Jun-2003 : Fixed bug (757303) in getDataRange() method (DG);
    100     * 02-Jul-2003 : Added patch from bug report 698646 (secondary axes for
    101     *               overlaid plots) (DG);
    102     * 23-Jul-2003 : Added support for multiple secondary datasets, axes and
    103     *               renderers (DG);
    104     * 27-Jul-2003 : Added support for stacked XY area charts (RA);
    105     * 19-Aug-2003 : Implemented Cloneable (DG);
    106     * 01-Sep-2003 : Fixed bug where change to secondary datasets didn't generate
    107     *               change event (797466) (DG)
    108     * 08-Sep-2003 : Added internationalization via use of properties
    109     *               resourceBundle (RFE 690236) (AL);
    110     * 08-Sep-2003 : Changed ValueAxis API (DG);
    111     * 08-Sep-2003 : Fixes for serialization (NB);
    112     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    113     * 17-Sep-2003 : Fixed zooming to include secondary domain axes (DG);
    114     * 18-Sep-2003 : Added getSecondaryDomainAxisCount() and
    115     *               getSecondaryRangeAxisCount() methods suggested by Eduardo
    116     *               Ramalho (RFE 808548) (DG);
    117     * 23-Sep-2003 : Split domain and range markers into foreground and
    118     *               background (DG);
    119     * 06-Oct-2003 : Fixed bug in clearDomainMarkers() and clearRangeMarkers()
    120     *               methods.  Fixed bug (815876) in addSecondaryRangeMarker()
    121     *               method.  Added new addSecondaryDomainMarker methods (see bug
    122     *               id 815869) (DG);
    123     * 10-Nov-2003 : Added getSecondaryDomain/RangeAxisMappedToDataset() methods
    124     *               requested by Eduardo Ramalho (DG);
    125     * 24-Nov-2003 : Removed unnecessary notification when updating axis anchor
    126     *               values (DG);
    127     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
    128     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    129     * 12-Mar-2004 : Fixed bug where primary renderer is always used to determine
    130     *               range type (DG);
    131     * 22-Mar-2004 : Fixed cloning bug (DG);
    132     * 23-Mar-2004 : Fixed more cloning bugs (DG);
    133     * 07-Apr-2004 : Fixed problem with axis range when the secondary renderer is
    134     *               stacked, see this post in the forum:
    135     *               http://www.jfree.org/phpBB2/viewtopic.php?t=8204 (DG);
    136     * 07-Apr-2004 : Added get/setDatasetRenderingOrder() methods (DG);
    137     * 26-Apr-2004 : Added option to fill quadrant areas in the background of the
    138     *               plot (DG);
    139     * 27-Apr-2004 : Removed major distinction between primary and secondary
    140     *               datasets, renderers and axes (DG);
    141     * 30-Apr-2004 : Modified to make use of the new getRangeExtent() method in the
    142     *               renderer interface (DG);
    143     * 13-May-2004 : Added optional fixedLegendItems attribute (DG);
    144     * 19-May-2004 : Added indexOf() method (DG);
    145     * 03-Jun-2004 : Fixed zooming bug (DG);
    146     * 18-Aug-2004 : Added removedAnnotation() method (by tkram01) (DG);
    147     * 05-Oct-2004 : Modified storage type for dataset-to-axis maps (DG);
    148     * 06-Oct-2004 : Modified getDataRange() method to use renderer to determine
    149     *               the x-value range (now matches behaviour for y-values).  Added
    150     *               getDomainAxisIndex() method (DG);
    151     * 12-Nov-2004 : Implemented new Zoomable interface (DG);
    152     * 25-Nov-2004 : Small update to clone() implementation (DG);
    153     * 22-Feb-2005 : Changed axis offsets from Spacer --> RectangleInsets (DG);
    154     * 24-Feb-2005 : Added indexOf(XYItemRenderer) method (DG);
    155     * 21-Mar-2005 : Register plot as change listener in setRenderer() method (DG);
    156     * 21-Apr-2005 : Added get/setSeriesRenderingOrder() methods (ET);
    157     * 26-Apr-2005 : Removed LOGGER (DG);
    158     * 04-May-2005 : Fixed serialization of domain and range markers (DG);
    159     * 05-May-2005 : Removed unused draw() method (DG);
    160     * 20-May-2005 : Added setDomainAxes() and setRangeAxes() methods, as per
    161     *               RFE 1183100 (DG);
    162     * 01-Jun-2005 : Upon deserialization, register plot as a listener with its
    163     *               axes, dataset(s) and renderer(s) - see patch 1209475 (DG);
    164     * 01-Jun-2005 : Added clearDomainMarkers(int) method to match
    165     *               clearRangeMarkers(int) (DG);
    166     * 06-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
    167     * 09-Jun-2005 : Added setRenderers(), as per RFE 1183100 (DG);
    168     * 06-Jul-2005 : Fixed crosshair bug (id = 1233336) (DG);
    169     * ------------- JFREECHART 1.0.x ---------------------------------------------
    170     * 26-Jan-2006 : Added getAnnotations() method (DG);
    171     * 05-Sep-2006 : Added MarkerChangeEvent support (DG);
    172     * 13-Oct-2006 : Fixed initialisation of CrosshairState - see bug report
    173     *               1565168 (DG);
    174     * 22-Nov-2006 : Fixed equals() and cloning() for quadrant attributes, plus
    175     *               API doc updates (DG);
    176     * 29-Nov-2006 : Added argument checks (DG);
    177     * 15-Jan-2007 : Fixed bug in drawRangeMarkers() (DG);
    178     * 07-Feb-2007 : Fixed bug 1654215, renderer with no dataset (DG);
    179     * 26-Feb-2007 : Added missing setDomainAxisLocation() and
    180     *               setRangeAxisLocation() methods (DG);
    181     * 02-Mar-2007 : Fix for crosshair positioning with horizontal orientation
    182     *               (see patch 1671648 by Sergei Ivanov) (DG);
    183     * 13-Mar-2007 : Added null argument checks for crosshair attributes (DG);
    184     * 23-Mar-2007 : Added domain zero base line facility (DG);
    185     * 04-May-2007 : Render only visible data items if possible (DG);
    186     * 24-May-2007 : Fixed bug in render method for an empty series (DG);
    187     * 07-Jun-2007 : Modified drawBackground() to pass orientation to
    188     *               fillBackground() for handling GradientPaint (DG);
    189     * 24-Sep-2007 : Added new zoom methods (DG);
    190     * 26-Sep-2007 : Include index value in IllegalArgumentExceptions (DG);
    191     * 05-Nov-2007 : Applied patch 1823697, by Richard West, for removal of domain
    192     *               and range markers (DG);
    193     * 12-Nov-2007 : Fixed bug in equals() method for domain and range tick
    194     *               band paint attributes (DG);
    195     * 27-Nov-2007 : Added new setFixedDomain/RangeAxisSpace() methods (DG);
    196     * 04-Jan-2008 : Fix for quadrant painting error - see patch 1849564 (DG);
    197     * 25-Mar-2008 : Added new methods with optional notification - see patch
    198     *               1913751 (DG);
    199     * 07-Apr-2008 : Fixed NPE in removeDomainMarker() and
    200     *               removeRangeMarker() (DG);
    201     * 22-May-2008 : Modified calculateAxisSpace() to process range axes first,
    202     *               then adjust the plot area before calculating the space
    203     *               for the domain axes (DG);
    204     *
    205     */
    206
    207    package org.jfree.chart.plot;
    208
    209    import java.awt.AlphaComposite;
    210    import java.awt.BasicStroke;
    211    import java.awt.Color;
    212    import java.awt.Composite;
    213    import java.awt.Graphics2D;
    214    import java.awt.Paint;
    215    import java.awt.Shape;
    216    import java.awt.Stroke;
    217    import java.awt.geom.Line2D;
    218    import java.awt.geom.Point2D;
    219    import java.awt.geom.Rectangle2D;
    220    import java.io.IOException;
    221    import java.io.ObjectInputStream;
    222    import java.io.ObjectOutputStream;
    223    import java.io.Serializable;
    224    import java.util.ArrayList;
    225    import java.util.Collection;
    226    import java.util.Collections;
    227    import java.util.HashMap;
    228    import java.util.Iterator;
    229    import java.util.List;
    230    import java.util.Map;
    231    import java.util.ResourceBundle;
    232    import java.util.Set;
    233    import java.util.TreeMap;
    234
    235    import org.jfree.chart.LegendItem;
    236    import org.jfree.chart.LegendItemCollection;
    237    import org.jfree.chart.annotations.XYAnnotation;
    238    import org.jfree.chart.axis.Axis;
    239    import org.jfree.chart.axis.AxisCollection;
    240    import org.jfree.chart.axis.AxisLocation;
    241    import org.jfree.chart.axis.AxisSpace;
    242    import org.jfree.chart.axis.AxisState;
    243    import org.jfree.chart.axis.ValueAxis;
    244    import org.jfree.chart.axis.ValueTick;
    245    import org.jfree.chart.event.ChartChangeEventType;
    246    import org.jfree.chart.event.PlotChangeEvent;
    247    import org.jfree.chart.event.RendererChangeEvent;
    248    import org.jfree.chart.event.RendererChangeListener;
    249    import org.jfree.chart.renderer.RendererUtilities;
    250    import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
    251    import org.jfree.chart.renderer.xy.XYItemRenderer;
    252    import org.jfree.chart.renderer.xy.XYItemRendererState;
    253    import org.jfree.data.Range;
    254    import org.jfree.data.general.Dataset;
    255    import org.jfree.data.general.DatasetChangeEvent;
    256    import org.jfree.data.general.DatasetUtilities;
    257    import org.jfree.data.xy.XYDataset;
    258    import org.jfree.io.SerialUtilities;
    259    import org.jfree.ui.Layer;
    260    import org.jfree.ui.RectangleEdge;
    261    import org.jfree.ui.RectangleInsets;
    262    import org.jfree.util.ObjectList;
    263    import org.jfree.util.ObjectUtilities;
    264    import org.jfree.util.PaintUtilities;
    265    import org.jfree.util.PublicCloneable;
    266
    267    /**
    268     * A general class for plotting data in the form of (x, y) pairs.  This plot can
    269     * use data from any class that implements the {@link XYDataset} interface.
    270     * <P>
    271     * <code>XYPlot</code> makes use of an {@link XYItemRenderer} to draw each point
    272     * on the plot.  By using different renderers, various chart types can be
    273     * produced.
    274     * <p>
    275     * The {@link org.jfree.chart.ChartFactory} class contains static methods for
    276     * creating pre-configured charts.
    277     */
    278    public class XYPlot extends Plot implements ValueAxisPlot, Zoomable,
    279            RendererChangeListener, Cloneable, PublicCloneable, Serializable {
    280
    281        /** For serialization. */
    282        private static final long serialVersionUID = 7044148245716569264L;
    283
    284        /** The default grid line stroke. */
    285        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
    286                BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f,
    287                new float[] {2.0f, 2.0f}, 0.0f);
    288
    289        /** The default grid line paint. */
    290        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
    291
    292        /** The default crosshair visibility. */
    293        public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
    294
    295        /** The default crosshair stroke. */
    296        public static final Stroke DEFAULT_CROSSHAIR_STROKE
    297                = DEFAULT_GRIDLINE_STROKE;
    298
    299        /** The default crosshair paint. */
    300        public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
    301
    302        /** The resourceBundle for the localization. */
    303        protected static ResourceBundle localizationResources
    304                = ResourceBundle.getBundle(
    305                        "org.jfree.chart.plot.LocalizationBundle");
    306
    307        /** The plot orientation. */
    308        private PlotOrientation orientation;
    309
    310        /** The offset between the data area and the axes. */
    311        private RectangleInsets axisOffset;
    312
    313        /** The domain axis / axes (used for the x-values). */
    314        private ObjectList domainAxes;
    315
    316        /** The domain axis locations. */
    317        private ObjectList domainAxisLocations;
    318
    319        /** The range axis (used for the y-values). */
    320        private ObjectList rangeAxes;
    321
    322        /** The range axis location. */
    323        private ObjectList rangeAxisLocations;
    324
    325        /** Storage for the datasets. */
    326        private ObjectList datasets;
    327
    328        /** Storage for the renderers. */
    329        private ObjectList renderers;
    330
    331        /**
    332         * Storage for keys that map datasets/renderers to domain axes.  If the
    333         * map contains no entry for a dataset, it is assumed to map to the
    334         * primary domain axis (index = 0).
    335         */
    336        private Map datasetToDomainAxisMap;
    337
    338        /**
    339         * Storage for keys that map datasets/renderers to range axes. If the
    340         * map contains no entry for a dataset, it is assumed to map to the
    341         * primary domain axis (index = 0).
    342         */
    343        private Map datasetToRangeAxisMap;
    344
    345        /** The origin point for the quadrants (if drawn). */
    346        private transient Point2D quadrantOrigin = new Point2D.Double(0.0, 0.0);
    347
    348        /** The paint used for each quadrant. */
    349        private transient Paint[] quadrantPaint
    350                = new Paint[] {null, null, null, null};
    351
    352        /** A flag that controls whether the domain grid-lines are visible. */
    353        private boolean domainGridlinesVisible;
    354
    355        /** The stroke used to draw the domain grid-lines. */
    356        private transient Stroke domainGridlineStroke;
    357
    358        /** The paint used to draw the domain grid-lines. */
    359        private transient Paint domainGridlinePaint;
    360
    361        /** A flag that controls whether the range grid-lines are visible. */
    362        private boolean rangeGridlinesVisible;
    363
    364        /** The stroke used to draw the range grid-lines. */
    365        private transient Stroke rangeGridlineStroke;
    366
    367        /** The paint used to draw the range grid-lines. */
    368        private transient Paint rangeGridlinePaint;
    369
    370        /**
    371         * A flag that controls whether or not the zero baseline against the domain
    372         * axis is visible.
    373         *
    374         * @since 1.0.5
    375         */
    376        private boolean domainZeroBaselineVisible;
    377
    378        /**
    379         * The stroke used for the zero baseline against the domain axis.
    380         *
    381         * @since 1.0.5
    382         */
    383        private transient Stroke domainZeroBaselineStroke;
    384
    385        /**
    386         * The paint used for the zero baseline against the domain axis.
    387         *
    388         * @since 1.0.5
    389         */
    390        private transient Paint domainZeroBaselinePaint;
    391
    392        /**
    393         * A flag that controls whether or not the zero baseline against the range
    394         * axis is visible.
    395         */
    396        private boolean rangeZeroBaselineVisible;
    397
    398        /** The stroke used for the zero baseline against the range axis. */
    399        private transient Stroke rangeZeroBaselineStroke;
    400
    401        /** The paint used for the zero baseline against the range axis. */
    402        private transient Paint rangeZeroBaselinePaint;
    403
    404        /** A flag that controls whether or not a domain crosshair is drawn..*/
    405        private boolean domainCrosshairVisible;
    406
    407        /** The domain crosshair value. */
    408        private double domainCrosshairValue;
    409
    410        /** The pen/brush used to draw the crosshair (if any). */
    411        private transient Stroke domainCrosshairStroke;
    412
    413        /** The color used to draw the crosshair (if any). */
    414        private transient Paint domainCrosshairPaint;
    415
    416        /**
    417         * A flag that controls whether or not the crosshair locks onto actual
    418         * data points.
    419         */
    420        private boolean domainCrosshairLockedOnData = true;
    421
    422        /** A flag that controls whether or not a range crosshair is drawn..*/
    423        private boolean rangeCrosshairVisible;
    424
    425        /** The range crosshair value. */
    426        private double rangeCrosshairValue;
    427
    428        /** The pen/brush used to draw the crosshair (if any). */
    429        private transient Stroke rangeCrosshairStroke;
    430
    431        /** The color used to draw the crosshair (if any). */
    432        private transient Paint rangeCrosshairPaint;
    433
    434        /**
    435         * A flag that controls whether or not the crosshair locks onto actual
    436         * data points.
    437         */
    438        private boolean rangeCrosshairLockedOnData = true;
    439
    440        /** A map of lists of foreground markers (optional) for the domain axes. */
    441        private Map foregroundDomainMarkers;
    442
    443        /** A map of lists of background markers (optional) for the domain axes. */
    444        private Map backgroundDomainMarkers;
    445
    446        /** A map of lists of foreground markers (optional) for the range axes. */
    447        private Map foregroundRangeMarkers;
    448
    449        /** A map of lists of background markers (optional) for the range axes. */
    450        private Map backgroundRangeMarkers;
    451
    452        /**
    453         * A (possibly empty) list of annotations for the plot.  The list should
    454         * be initialised in the constructor and never allowed to be
    455         * <code>null</code>.
    456         */
    457        private List annotations;
    458
    459        /** The paint used for the domain tick bands (if any). */
    460        private transient Paint domainTickBandPaint;
    461
    462        /** The paint used for the range tick bands (if any). */
    463        private transient Paint rangeTickBandPaint;
    464
    465        /** The fixed domain axis space. */
    466        private AxisSpace fixedDomainAxisSpace;
    467
    468        /** The fixed range axis space. */
    469        private AxisSpace fixedRangeAxisSpace;
    470
    471        /**
    472         * The order of the dataset rendering (REVERSE draws the primary dataset
    473         * last so that it appears to be on top).
    474         */
    475        private DatasetRenderingOrder datasetRenderingOrder
    476                = DatasetRenderingOrder.REVERSE;
    477
    478        /**
    479         * The order of the series rendering (REVERSE draws the primary series
    480         * last so that it appears to be on top).
    481         */
    482        private SeriesRenderingOrder seriesRenderingOrder
    483                = SeriesRenderingOrder.REVERSE;
    484
    485        /**
    486         * The weight for this plot (only relevant if this is a subplot in a
    487         * combined plot).
    488         */
    489        private int weight;
    490
    491        /**
    492         * An optional collection of legend items that can be returned by the
    493         * getLegendItems() method.
    494         */
    495        private LegendItemCollection fixedLegendItems;
    496
    497        /**
    498         * Creates a new <code>XYPlot</code> instance with no dataset, no axes and
    499         * no renderer.  You should specify these items before using the plot.
    500         */
    501        public XYPlot() {
    502            this(null, null, null, null);
    503        }
    504
    505        /**
    506         * Creates a new plot with the specified dataset, axes and renderer.  Any
    507         * of the arguments can be <code>null</code>, but in that case you should
    508         * take care to specify the value before using the plot (otherwise a
    509         * <code>NullPointerException</code> may be thrown).
    510         *
    511         * @param dataset  the dataset (<code>null</code> permitted).
    512         * @param domainAxis  the domain axis (<code>null</code> permitted).
    513         * @param rangeAxis  the range axis (<code>null</code> permitted).
    514         * @param renderer  the renderer (<code>null</code> permitted).
    515         */
    516        public XYPlot(XYDataset dataset,
    517                      ValueAxis domainAxis,
    518                      ValueAxis rangeAxis,
    519                      XYItemRenderer renderer) {
    520
    521            super();
    522
    523            this.orientation = PlotOrientation.VERTICAL;
    524            this.weight = 1;  // only relevant when this is a subplot
    525            this.axisOffset = RectangleInsets.ZERO_INSETS;
    526
    527            // allocate storage for datasets, axes and renderers (all optional)
    528            this.domainAxes = new ObjectList();
    529            this.domainAxisLocations = new ObjectList();
    530            this.foregroundDomainMarkers = new HashMap();
    531            this.backgroundDomainMarkers = new HashMap();
    532
    533            this.rangeAxes = new ObjectList();
    534            this.rangeAxisLocations = new ObjectList();
    535            this.foregroundRangeMarkers = new HashMap();
    536            this.backgroundRangeMarkers = new HashMap();
    537
    538            this.datasets = new ObjectList();
    539            this.renderers = new ObjectList();
    540
    541            this.datasetToDomainAxisMap = new TreeMap();
    542            this.datasetToRangeAxisMap = new TreeMap();
    543
    544            this.datasets.set(0, dataset);
    545            if (dataset != null) {
    546                dataset.addChangeListener(this);
    547            }
    548
    549            this.renderers.set(0, renderer);
    550            if (renderer != null) {
    551                renderer.setPlot(this);
    552                renderer.addChangeListener(this);
    553            }
    554
    555            this.domainAxes.set(0, domainAxis);
    556            this.mapDatasetToDomainAxis(0, 0);
    557            if (domainAxis != null) {
    558                domainAxis.setPlot(this);
    559                domainAxis.addChangeListener(this);
    560            }
    561            this.domainAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT);
    562
    563            this.rangeAxes.set(0, rangeAxis);
    564            this.mapDatasetToRangeAxis(0, 0);
    565            if (rangeAxis != null) {
    566                rangeAxis.setPlot(this);
    567                rangeAxis.addChangeListener(this);
    568            }
    569            this.rangeAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT);
    570
    571            configureDomainAxes();
    572            configureRangeAxes();
    573
    574            this.domainGridlinesVisible = true;
    575            this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
    576            this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
    577
    578            this.domainZeroBaselineVisible = false;
    579            this.domainZeroBaselinePaint = Color.black;
    580            this.domainZeroBaselineStroke = new BasicStroke(0.5f);
    581
    582            this.rangeGridlinesVisible = true;
    583            this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
    584            this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
    585
    586            this.rangeZeroBaselineVisible = false;
    587            this.rangeZeroBaselinePaint = Color.black;
    588            this.rangeZeroBaselineStroke = new BasicStroke(0.5f);
    589
    590            this.domainCrosshairVisible = false;
    591            this.domainCrosshairValue = 0.0;
    592            this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
    593            this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
    594
    595            this.rangeCrosshairVisible = false;
    596            this.rangeCrosshairValue = 0.0;
    597            this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
    598            this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
    599
    600            this.annotations = new java.util.ArrayList();
    601
    602        }
    603
    604        /**
    605         * Returns the plot type as a string.
    606         *
    607         * @return A short string describing the type of plot.
    608         */
    609        public String getPlotType() {
    610            return localizationResources.getString("XY_Plot");
    611        }
    612
    613        /**
    614         * Returns the orientation of the plot.
    615         *
    616         * @return The orientation (never <code>null</code>).
    617         *
    618         * @see #setOrientation(PlotOrientation)
    619         */
    620        public PlotOrientation getOrientation() {
    621            return this.orientation;
    622        }
    623
    624        /**
    625         * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
    626         * all registered listeners.
    627         *
    628         * @param orientation  the orientation (<code>null</code> not allowed).
    629         *
    630         * @see #getOrientation()
    631         */
    632        public void setOrientation(PlotOrientation orientation) {
    633            if (orientation == null) {
    634                throw new IllegalArgumentException("Null 'orientation' argument.");
    635            }
    636            if (orientation != this.orientation) {
    637                this.orientation = orientation;
    638                fireChangeEvent();
    639            }
    640        }
    641
    642        /**
    643         * Returns the axis offset.
    644         *
    645         * @return The axis offset (never <code>null</code>).
    646         *
    647         * @see #setAxisOffset(RectangleInsets)
    648         */
    649        public RectangleInsets getAxisOffset() {
    650            return this.axisOffset;
    651        }
    652
    653        /**
    654         * Sets the axis offsets (gap between the data area and the axes) and sends
    655         * a {@link PlotChangeEvent} to all registered listeners.
    656         *
    657         * @param offset  the offset (<code>null</code> not permitted).
    658         *
    659         * @see #getAxisOffset()
    660         */
    661        public void setAxisOffset(RectangleInsets offset) {
    662            if (offset == null) {
    663                throw new IllegalArgumentException("Null 'offset' argument.");
    664            }
    665            this.axisOffset = offset;
    666            fireChangeEvent();
    667        }
    668
    669        /**
    670         * Returns the domain axis with index 0.  If the domain axis for this plot
    671         * is <code>null</code>, then the method will return the parent plot's
    672         * domain axis (if there is a parent plot).
    673         *
    674         * @return The domain axis (possibly <code>null</code>).
    675         *
    676         * @see #getDomainAxis(int)
    677         * @see #setDomainAxis(ValueAxis)
    678         */
    679        public ValueAxis getDomainAxis() {
    680            return getDomainAxis(0);
    681        }
    682
    683        /**
    684         * Returns the domain axis with the specified index, or <code>null</code>.
    685         *
    686         * @param index  the axis index.
    687         *
    688         * @return The axis (<code>null</code> possible).
    689         *
    690         * @see #setDomainAxis(int, ValueAxis)
    691         */
    692        public ValueAxis getDomainAxis(int index) {
    693            ValueAxis result = null;
    694            if (index < this.domainAxes.size()) {
    695                result = (ValueAxis) this.domainAxes.get(index);
    696            }
    697            if (result == null) {
    698                Plot parent = getParent();
    699                if (parent instanceof XYPlot) {
    700                    XYPlot xy = (XYPlot) parent;
    701                    result = xy.getDomainAxis(index);
    702                }
    703            }
    704            return result;
    705        }
    706
    707        /**
    708         * Sets the domain axis for the plot and sends a {@link PlotChangeEvent}
    709         * to all registered listeners.
    710         *
    711         * @param axis  the new axis (<code>null</code> permitted).
    712         *
    713         * @see #getDomainAxis()
    714         * @see #setDomainAxis(int, ValueAxis)
    715         */
    716        public void setDomainAxis(ValueAxis axis) {
    717            setDomainAxis(0, axis);
    718        }
    719
    720        /**
    721         * Sets a domain axis and sends a {@link PlotChangeEvent} to all
    722         * registered listeners.
    723         *
    724         * @param index  the axis index.
    725         * @param axis  the axis (<code>null</code> permitted).
    726         *
    727         * @see #getDomainAxis(int)
    728         * @see #setRangeAxis(int, ValueAxis)
    729         */
    730        public void setDomainAxis(int index, ValueAxis axis) {
    731            setDomainAxis(index, axis, true);
    732        }
    733
    734        /**
    735         * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
    736         * all registered listeners.
    737         *
    738         * @param index  the axis index.
    739         * @param axis  the axis.
    740         * @param notify  notify listeners?
    741         *
    742         * @see #getDomainAxis(int)
    743         */
    744        public void setDomainAxis(int index, ValueAxis axis, boolean notify) {
    745            ValueAxis existing = getDomainAxis(index);
    746            if (existing != null) {
    747                existing.removeChangeListener(this);
    748            }
    749            if (axis != null) {
    750                axis.setPlot(this);
    751            }
    752            this.domainAxes.set(index, axis);
    753            if (axis != null) {
    754                axis.configure();
    755                axis.addChangeListener(this);
    756            }
    757            if (notify) {
    758                fireChangeEvent();
    759            }
    760        }
    761
    762        /**
    763         * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
    764         * to all registered listeners.
    765         *
    766         * @param axes  the axes (<code>null</code> not permitted).
    767         *
    768         * @see #setRangeAxes(ValueAxis[])
    769         */
    770        public void setDomainAxes(ValueAxis[] axes) {
    771            for (int i = 0; i < axes.length; i++) {
    772                setDomainAxis(i, axes[i], false);
    773            }
    774            fireChangeEvent();
    775        }
    776
    777        /**
    778         * Returns the location of the primary domain axis.
    779         *
    780         * @return The location (never <code>null</code>).
    781         *
    782         * @see #setDomainAxisLocation(AxisLocation)
    783         */
    784        public AxisLocation getDomainAxisLocation() {
    785            return (AxisLocation) this.domainAxisLocations.get(0);
    786        }
    787
    788        /**
    789         * Sets the location of the primary domain axis and sends a
    790         * {@link PlotChangeEvent} to all registered listeners.
    791         *
    792         * @param location  the location (<code>null</code> not permitted).
    793         *
    794         * @see #getDomainAxisLocation()
    795         */
    796        public void setDomainAxisLocation(AxisLocation location) {
    797            // delegate...
    798            setDomainAxisLocation(0, location, true);
    799        }
    800
    801        /**
    802         * Sets the location of the domain axis and, if requested, sends a
    803         * {@link PlotChangeEvent} to all registered listeners.
    804         *
    805         * @param location  the location (<code>null</code> not permitted).
    806         * @param notify  notify listeners?
    807         *
    808         * @see #getDomainAxisLocation()
    809         */
    810        public void setDomainAxisLocation(AxisLocation location, boolean notify) {
    811            // delegate...
    812            setDomainAxisLocation(0, location, notify);
    813        }
    814
    815        /**
    816         * Returns the edge for the primary domain axis (taking into account the
    817         * plot's orientation).
    818         *
    819         * @return The edge.
    820         *
    821         * @see #getDomainAxisLocation()
    822         * @see #getOrientation()
    823         */
    824        public RectangleEdge getDomainAxisEdge() {
    825            return Plot.resolveDomainAxisLocation(getDomainAxisLocation(),
    826                    this.orientation);
    827        }
    828
    829        /**
    830         * Returns the number of domain axes.
    831         *
    832         * @return The axis count.
    833         *
    834         * @see #getRangeAxisCount()
    835         */
    836        public int getDomainAxisCount() {
    837            return this.domainAxes.size();
    838        }
    839
    840        /**
    841         * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
    842         * to all registered listeners.
    843         *
    844         * @see #clearRangeAxes()
    845         */
    846        public void clearDomainAxes() {
    847            for (int i = 0; i < this.domainAxes.size(); i++) {
    848                ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
    849                if (axis != null) {
    850                    axis.removeChangeListener(this);
    851                }
    852            }
    853            this.domainAxes.clear();
    854            fireChangeEvent();
    855        }
    856
    857        /**
    858         * Configures the domain axes.
    859         */
    860        public void configureDomainAxes() {
    861            for (int i = 0; i < this.domainAxes.size(); i++) {
    862                ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
    863                if (axis != null) {
    864                    axis.configure();
    865                }
    866            }
    867        }
    868
    869        /**
    870         * Returns the location for a domain axis.  If this hasn't been set
    871         * explicitly, the method returns the location that is opposite to the
    872         * primary domain axis location.
    873         *
    874         * @param index  the axis index.
    875         *
    876         * @return The location (never <code>null</code>).
    877         *
    878         * @see #setDomainAxisLocation(int, AxisLocation)
    879         */
    880        public AxisLocation getDomainAxisLocation(int index) {
    881            AxisLocation result = null;
    882            if (index < this.domainAxisLocations.size()) {
    883                result = (AxisLocation) this.domainAxisLocations.get(index);
    884            }
    885            if (result == null) {
    886                result = AxisLocation.getOpposite(getDomainAxisLocation());
    887            }
    888            return result;
    889        }
    890
    891        /**
    892         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
    893         * to all registered listeners.
    894         *
    895         * @param index  the axis index.
    896         * @param location  the location (<code>null</code> not permitted for index
    897         *     0).
    898         *
    899         * @see #getDomainAxisLocation(int)
    900         */
    901        public void setDomainAxisLocation(int index, AxisLocation location) {
    902            // delegate...
    903            setDomainAxisLocation(index, location, true);
    904        }
    905
    906        /**
    907         * Sets the axis location for a domain axis and, if requested, sends a
    908         * {@link PlotChangeEvent} to all registered listeners.
    909         *
    910         * @param index  the axis index.
    911         * @param location  the location (<code>null</code> not permitted for
    912         *     index 0).
    913         * @param notify  notify listeners?
    914         *
    915         * @since 1.0.5
    916         *
    917         * @see #getDomainAxisLocation(int)
    918         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
    919         */
    920        public void setDomainAxisLocation(int index, AxisLocation location,
    921                boolean notify) {
    922
    923            if (index == 0 && location == null) {
    924                throw new IllegalArgumentException(
    925                        "Null 'location' for index 0 not permitted.");
    926            }
    927            this.domainAxisLocations.set(index, location);
    928            if (notify) {
    929                fireChangeEvent();
    930            }
    931        }
    932
    933        /**
    934         * Returns the edge for a domain axis.
    935         *
    936         * @param index  the axis index.
    937         *
    938         * @return The edge.
    939         *
    940         * @see #getRangeAxisEdge(int)
    941         */
    942        public RectangleEdge getDomainAxisEdge(int index) {
    943            AxisLocation location = getDomainAxisLocation(index);
    944            RectangleEdge result = Plot.resolveDomainAxisLocation(location,
    945                    this.orientation);
    946            if (result == null) {
    947                result = RectangleEdge.opposite(getDomainAxisEdge());
    948            }
    949            return result;
    950        }
    951
    952        /**
    953         * Returns the range axis for the plot.  If the range axis for this plot is
    954         * <code>null</code>, then the method will return the parent plot's range
    955         * axis (if there is a parent plot).
    956         *
    957         * @return The range axis.
    958         *
    959         * @see #getRangeAxis(int)
    960         * @see #setRangeAxis(ValueAxis)
    961         */
    962        public ValueAxis getRangeAxis() {
    963            return getRangeAxis(0);
    964        }
    965
    966        /**
    967         * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
    968         * all registered listeners.
    969         *
    970         * @param axis  the axis (<code>null</code> permitted).
    971         *
    972         * @see #getRangeAxis()
    973         * @see #setRangeAxis(int, ValueAxis)
    974         */
    975        public void setRangeAxis(ValueAxis axis)  {
    976
    977            if (axis != null) {
    978                axis.setPlot(this);
    979            }
    980
    981            // plot is likely registered as a listener with the existing axis...
    982            ValueAxis existing = getRangeAxis();
    983            if (existing != null) {
    984                existing.removeChangeListener(this);
    985            }
    986
    987            this.rangeAxes.set(0, axis);
    988            if (axis != null) {
    989                axis.configure();
    990                axis.addChangeListener(this);
    991            }
    992            fireChangeEvent();
    993
    994        }
    995
    996        /**
    997         * Returns the location of the primary range axis.
    998         *
    999         * @return The location (never <code>null</code>).
    1000         *
    1001         * @see #setRangeAxisLocation(AxisLocation)
    1002         */
    1003        public AxisLocation getRangeAxisLocation() {
    1004            return (AxisLocation) this.rangeAxisLocations.get(0);
    1005        }
    1006
    1007        /**
    1008         * Sets the location of the primary range axis and sends a
    1009         * {@link PlotChangeEvent} to all registered listeners.
    1010         *
    1011         * @param location  the location (<code>null</code> not permitted).
    1012         *
    1013         * @see #getRangeAxisLocation()
    1014         */
    1015        public void setRangeAxisLocation(AxisLocation location) {
    1016            // delegate...
    1017            setRangeAxisLocation(0, location, true);
    1018        }
    1019
    1020        /**
    1021         * Sets the location of the primary range axis and, if requested, sends a
    1022         * {@link PlotChangeEvent} to all registered listeners.
    1023         *
    1024         * @param location  the location (<code>null</code> not permitted).
    1025         * @param notify  notify listeners?
    1026         *
    1027         * @see #getRangeAxisLocation()
    1028         */
    1029        public void setRangeAxisLocation(AxisLocation location, boolean notify) {
    1030            // delegate...
    1031            setRangeAxisLocation(0, location, notify);
    1032        }
    1033
    1034        /**
    1035         * Returns the edge for the primary range axis.
    1036         *
    1037         * @return The range axis edge.
    1038         *
    1039         * @see #getRangeAxisLocation()
    1040         * @see #getOrientation()
    1041         */
    1042        public RectangleEdge getRangeAxisEdge() {
    1043            return Plot.resolveRangeAxisLocation(getRangeAxisLocation(),
    1044                    this.orientation);
    1045        }
    1046
    1047        /**
    1048         * Returns a range axis.
    1049         *
    1050         * @param index  the axis index.
    1051         *
    1052         * @return The axis (<code>null</code> possible).
    1053         *
    1054         * @see #setRangeAxis(int, ValueAxis)
    1055         */
    1056        public ValueAxis getRangeAxis(int index) {
    1057            ValueAxis result = null;
    1058            if (index < this.rangeAxes.size()) {
    1059                result = (ValueAxis) this.rangeAxes.get(index);
    1060            }
    1061            if (result == null) {
    1062                Plot parent = getParent();
    1063                if (parent instanceof XYPlot) {
    1064                    XYPlot xy = (XYPlot) parent;
    1065                    result = xy.getRangeAxis(index);
    1066                }
    1067            }
    1068            return result;
    1069        }
    1070
    1071        /**
    1072         * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
    1073         * listeners.
    1074         *
    1075         * @param index  the axis index.
    1076         * @param axis  the axis (<code>null</code> permitted).
    1077         *
    1078         * @see #getRangeAxis(int)
    1079         */
    1080        public void setRangeAxis(int index, ValueAxis axis) {
    1081            setRangeAxis(index, axis, true);
    1082        }
    1083
    1084        /**
    1085         * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
    1086         * all registered listeners.
    1087         *
    1088         * @param index  the axis index.
    1089         * @param axis  the axis (<code>null</code> permitted).
    1090         * @param notify  notify listeners?
    1091         *
    1092         * @see #getRangeAxis(int)
    1093         */
    1094        public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
    1095            ValueAxis existing = getRangeAxis(index);
    1096            if (existing != null) {
    1097                existing.removeChangeListener(this);
    1098            }
    1099            if (axis != null) {
    1100                axis.setPlot(this);
    1101            }
    1102            this.rangeAxes.set(index, axis);
    1103            if (axis != null) {
    1104                axis.configure();
    1105                axis.addChangeListener(this);
    1106            }
    1107            if (notify) {
    1108                fireChangeEvent();
    1109            }
    1110        }
    1111
    1112        /**
    1113         * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
    1114         * to all registered listeners.
    1115         *
    1116         * @param axes  the axes (<code>null</code> not permitted).
    1117         *
    1118         * @see #setDomainAxes(ValueAxis[])
    1119         */
    1120        public void setRangeAxes(ValueAxis[] axes) {
    1121            for (int i = 0; i < axes.length; i++) {
    1122                setRangeAxis(i, axes[i], false);
    1123            }
    1124            fireChangeEvent();
    1125        }
    1126
    1127        /**
    1128         * Returns the number of range axes.
    1129         *
    1130         * @return The axis count.
    1131         *
    1132         * @see #getDomainAxisCount()
    1133         */
    1134        public int getRangeAxisCount() {
    1135            return this.rangeAxes.size();
    1136        }
    1137
    1138        /**
    1139         * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
    1140         * to all registered listeners.
    1141         *
    1142         * @see #clearDomainAxes()
    1143         */
    1144        public void clearRangeAxes() {
    1145            for (int i = 0; i < this.rangeAxes.size(); i++) {
    1146                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
    1147                if (axis != null) {
    1148                    axis.removeChangeListener(this);
    1149                }
    1150            }
    1151            this.rangeAxes.clear();
    1152            fireChangeEvent();
    1153        }
    1154
    1155        /**
    1156         * Configures the range axes.
    1157         *
    1158         * @see #configureDomainAxes()
    1159         */
    1160        public void configureRangeAxes() {
    1161            for (int i = 0; i < this.rangeAxes.size(); i++) {
    1162                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
    1163                if (axis != null) {
    1164                    axis.configure();
    1165                }
    1166            }
    1167        }
    1168
    1169        /**
    1170         * Returns the location for a range axis.  If this hasn't been set
    1171         * explicitly, the method returns the location that is opposite to the
    1172         * primary range axis location.
    1173         *
    1174         * @param index  the axis index.
    1175         *
    1176         * @return The location (never <code>null</code>).
    1177         *
    1178         * @see #setRangeAxisLocation(int, AxisLocation)
    1179         */
    1180        public AxisLocation getRangeAxisLocation(int index) {
    1181            AxisLocation result = null;
    1182            if (index < this.rangeAxisLocations.size()) {
    1183                result = (AxisLocation) this.rangeAxisLocations.get(index);
    1184            }
    1185            if (result == null) {
    1186                result = AxisLocation.getOpposite(getRangeAxisLocation());
    1187            }
    1188            return result;
    1189        }
    1190
    1191        /**
    1192         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
    1193         * to all registered listeners.
    1194         *
    1195         * @param index  the axis index.
    1196         * @param location  the location (<code>null</code> permitted).
    1197         *
    1198         * @see #getRangeAxisLocation(int)
    1199         */
    1200        public void setRangeAxisLocation(int index, AxisLocation location) {
    1201            // delegate...
    1202            setRangeAxisLocation(index, location, true);
    1203        }
    1204
    1205        /**
    1206         * Sets the axis location for a domain axis and, if requested, sends a
    1207         * {@link PlotChangeEvent} to all registered listeners.
    1208         *
    1209         * @param index  the axis index.
    1210         * @param location  the location (<code>null</code> not permitted for
    1211         *     index 0).
    1212         * @param notify  notify listeners?
    1213         *
    1214         * @since 1.0.5
    1215         *
    1216         * @see #getRangeAxisLocation(int)
    1217         * @see #setDomainAxisLocation(int, AxisLocation, boolean)
    1218         */
    1219        public void setRangeAxisLocation(int index, AxisLocation location,
    1220                boolean notify) {
    1221
    1222            if (index == 0 && location == null) {
    1223                throw new IllegalArgumentException(
    1224                        "Null 'location' for index 0 not permitted.");
    1225            }
    1226            this.rangeAxisLocations.set(index, location);
    1227            if (notify) {
    1228                fireChangeEvent();
    1229            }
    1230        }
    1231
    1232        /**
    1233         * Returns the edge for a range axis.
    1234         *
    1235         * @param index  the axis index.
    1236         *
    1237         * @return The edge.
    1238         *
    1239         * @see #getRangeAxisLocation(int)
    1240         * @see #getOrientation()
    1241         */
    1242        public RectangleEdge getRangeAxisEdge(int index) {
    1243            AxisLocation location = getRangeAxisLocation(index);
    1244            RectangleEdge result = Plot.resolveRangeAxisLocation(location,
    1245                    this.orientation);
    1246            if (result == null) {
    1247                result = RectangleEdge.opposite(getRangeAxisEdge());
    1248            }
    1249            return result;
    1250        }
    1251
    1252        /**
    1253         * Returns the primary dataset for the plot.
    1254         *
    1255         * @return The primary dataset (possibly <code>null</code>).
    1256         *
    1257         * @see #getDataset(int)
    1258         * @see #setDataset(XYDataset)
    1259         */
    1260        public XYDataset getDataset() {
    1261            return getDataset(0);
    1262        }
    1263
    1264        /**
    1265         * Returns a dataset.
    1266         *
    1267         * @param index  the dataset index.
    1268         *
    1269         * @return The dataset (possibly <code>null</code>).
    1270         *
    1271         * @see #setDataset(int, XYDataset)
    1272         */
    1273        public XYDataset getDataset(int index) {
    1274            XYDataset result = null;
    1275            if (this.datasets.size() > index) {
    1276                result = (XYDataset) this.datasets.get(index);
    1277            }
    1278            return result;
    1279        }
    1280
    1281        /**
    1282         * Sets the primary dataset for the plot, replacing the existing dataset if
    1283         * there is one.
    1284         *
    1285         * @param dataset  the dataset (<code>null</code> permitted).
    1286         *
    1287         * @see #getDataset()
    1288         * @see #setDataset(int, XYDataset)
    1289         */
    1290        public void setDataset(XYDataset dataset) {
    1291            setDataset(0, dataset);
    1292        }
    1293
    1294        /**
    1295         * Sets a dataset for the plot.
    1296         *
    1297         * @param index  the dataset index.
    1298         * @param dataset  the dataset (<code>null</code> permitted).
    1299         *
    1300         * @see #getDataset(int)
    1301         */
    1302        public void setDataset(int index, XYDataset dataset) {
    1303            XYDataset existing = getDataset(index);
    1304            if (existing != null) {
    1305                existing.removeChangeListener(this);
    1306            }
    1307            this.datasets.set(index, dataset);
    1308            if (dataset != null) {
    1309                dataset.addChangeListener(this);
    1310            }
    1311
    1312            // send a dataset change event to self...
    1313            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
    1314            datasetChanged(event);
    1315        }
    1316
    1317        /**
    1318         * Returns the number of datasets.
    1319         *
    1320         * @return The number of datasets.
    1321         */
    1322        public int getDatasetCount() {
    1323            return this.datasets.size();
    1324        }
    1325
    1326        /**
    1327         * Returns the index of the specified dataset, or <code>-1</code> if the
    1328         * dataset does not belong to the plot.
    1329         *
    1330         * @param dataset  the dataset (<code>null</code> not permitted).
    1331         *
    1332         * @return The index.
    1333         */
    1334        public int indexOf(XYDataset dataset) {
    1335            int result = -1;
    1336            for (int i = 0; i < this.datasets.size(); i++) {
    1337                if (dataset == this.datasets.get(i)) {
    1338                    result = i;
    1339                    break;
    1340                }
    1341            }
    1342            return result;
    1343        }
    1344
    1345        /**
    1346         * Maps a dataset to a particular domain axis.  All data will be plotted
    1347         * against axis zero by default, no mapping is required for this case.
    1348         *
    1349         * @param index  the dataset index (zero-based).
    1350         * @param axisIndex  the axis index.
    1351         *
    1352         * @see #mapDatasetToRangeAxis(int, int)
    1353         */
    1354        public void mapDatasetToDomainAxis(int index, int axisIndex) {
    1355            this.datasetToDomainAxisMap.put(new Integer(index),
    1356                    new Integer(axisIndex));
    1357            // fake a dataset change event to update axes...
    1358            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
    1359        }
    1360
    1361        /**
    1362         * Maps a dataset to a particular range axis.  All data will be plotted
    1363         * against axis zero by default, no mapping is required for this case.
    1364         *
    1365         * @param index  the dataset index (zero-based).
    1366         * @param axisIndex  the axis index.
    1367         *
    1368         * @see #mapDatasetToDomainAxis(int, int)
    1369         */
    1370        public void mapDatasetToRangeAxis(int index, int axisIndex) {
    1371            this.datasetToRangeAxisMap.put(new Integer(index),
    1372                    new Integer(axisIndex));
    1373            // fake a dataset change event to update axes...
    1374            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
    1375        }
    1376
    1377        /**
    1378         * Returns the renderer for the primary dataset.
    1379         *
    1380         * @return The item renderer (possibly <code>null</code>).
    1381         *
    1382         * @see #setRenderer(XYItemRenderer)
    1383         */
    1384        public XYItemRenderer getRenderer() {
    1385            return getRenderer(0);
    1386        }
    1387
    1388        /**
    1389         * Returns the renderer for a dataset, or <code>null</code>.
    1390         *
    1391         * @param index  the renderer index.
    1392         *
    1393         * @return The renderer (possibly <code>null</code>).
    1394         *
    1395         * @see #setRenderer(int, XYItemRenderer)
    1396         */
    1397        public XYItemRenderer getRenderer(int index) {
    1398            XYItemRenderer result = null;
    1399            if (this.renderers.size() > index) {
    1400                result = (XYItemRenderer) this.renderers.get(index);
    1401            }
    1402            return result;
    1403
    1404        }
    1405
    1406        /**
    1407         * Sets the renderer for the primary dataset and sends a
    1408         * {@link PlotChangeEvent} to all registered listeners.  If the renderer
    1409         * is set to <code>null</code>, no data will be displayed.
    1410         *
    1411         * @param renderer  the renderer (<code>null</code> permitted).
    1412         *
    1413         * @see #getRenderer()
    1414         */
    1415        public void setRenderer(XYItemRenderer renderer) {
    1416            setRenderer(0, renderer);
    1417        }
    1418
    1419        /**
    1420         * Sets a renderer and sends a {@link PlotChangeEvent} to all
    1421         * registered listeners.
    1422         *
    1423         * @param index  the index.
    1424         * @param renderer  the renderer.
    1425         *
    1426         * @see #getRenderer(int)
    1427         */
    1428        public void setRenderer(int index, XYItemRenderer renderer) {
    1429            setRenderer(index, renderer, true);
    1430        }
    1431
    1432        /**
    1433         * Sets a renderer and sends a {@link PlotChangeEvent} to all
    1434         * registered listeners.
    1435         *
    1436         * @param index  the index.
    1437         * @param renderer  the renderer.
    1438         * @param notify  notify listeners?
    1439         *
    1440         * @see #getRenderer(int)
    1441         */
    1442        public void setRenderer(int index, XYItemRenderer renderer,
    1443                                boolean notify) {
    1444            XYItemRenderer existing = getRenderer(index);
    1445            if (existing != null) {
    1446                existing.removeChangeListener(this);
    1447            }
    1448            this.renderers.set(index, renderer);
    1449            if (renderer != null) {
    1450                renderer.setPlot(this);
    1451                renderer.addChangeListener(this);
    1452            }
    1453            configureDomainAxes();
    1454            configureRangeAxes();
    1455            if (notify) {
    1456                fireChangeEvent();
    1457            }
    1458        }
    1459
    1460        /**
    1461         * Sets the renderers for this plot and sends a {@link PlotChangeEvent}
    1462         * to all registered listeners.
    1463         *
    1464         * @param renderers  the renderers (<code>null</code> not permitted).
    1465         */
    1466        public void setRenderers(XYItemRenderer[] renderers) {
    1467            for (int i = 0; i < renderers.length; i++) {
    1468                setRenderer(i, renderers[i], false);
    1469            }
    1470            fireChangeEvent();
    1471        }
    1472
    1473        /**
    1474         * Returns the dataset rendering order.
    1475         *
    1476         * @return The order (never <code>null</code>).
    1477         *
    1478         * @see #setDatasetRenderingOrder(DatasetRenderingOrder)
    1479         */
    1480        public DatasetRenderingOrder getDatasetRenderingOrder() {
    1481            return this.datasetRenderingOrder;
    1482        }
    1483
    1484        /**
    1485         * Sets the rendering order and sends a {@link PlotChangeEvent} to all
    1486         * registered listeners.  By default, the plot renders the primary dataset
    1487         * last (so that the primary dataset overlays the secondary datasets).
    1488         * You can reverse this if you want to.
    1489         *
    1490         * @param order  the rendering order (<code>null</code> not permitted).
    1491         *
    1492         * @see #getDatasetRenderingOrder()
    1493         */
    1494        public void setDatasetRenderingOrder(DatasetRenderingOrder order) {
    1495            if (order == null) {
    1496                throw new IllegalArgumentException("Null 'order' argument.");
    1497            }
    1498            this.datasetRenderingOrder = order;
    1499            fireChangeEvent();
    1500        }
    1501
    1502        /**
    1503         * Returns the series rendering order.
    1504         *
    1505         * @return the order (never <code>null</code>).
    1506         *
    1507         * @see #setSeriesRenderingOrder(SeriesRenderingOrder)
    1508         */
    1509        public SeriesRenderingOrder getSeriesRenderingOrder() {
    1510            return this.seriesRenderingOrder;
    1511        }
    1512
    1513        /**
    1514         * Sets the series order and sends a {@link PlotChangeEvent} to all
    1515         * registered listeners.  By default, the plot renders the primary series
    1516         * last (so that the primary series appears to be on top).
    1517         * You can reverse this if you want to.
    1518         *
    1519         * @param order  the rendering order (<code>null</code> not permitted).
    1520         *
    1521         * @see #getSeriesRenderingOrder()
    1522         */
    1523        public void setSeriesRenderingOrder(SeriesRenderingOrder order) {
    1524            if (order == null) {
    1525                throw new IllegalArgumentException("Null 'order' argument.");
    1526            }
    1527            this.seriesRenderingOrder = order;
    1528            fireChangeEvent();
    1529        }
    1530
    1531        /**
    1532         * Returns the index of the specified renderer, or <code>-1</code> if the
    1533         * renderer is not assigned to this plot.
    1534         *
    1535         * @param renderer  the renderer (<code>null</code> permitted).
    1536         *
    1537         * @return The renderer index.
    1538         */
    1539        public int getIndexOf(XYItemRenderer renderer) {
    1540            return this.renderers.indexOf(renderer);
    1541        }
    1542
    1543        /**
    1544         * Returns the renderer for the specified dataset.  The code first
    1545         * determines the index of the dataset, then checks if there is a
    1546         * renderer with the same index (if not, the method returns renderer(0).
    1547         *
    1548         * @param dataset  the dataset (<code>null</code> permitted).
    1549         *
    1550         * @return The renderer (possibly <code>null</code>).
    1551         */
    1552        public XYItemRenderer getRendererForDataset(XYDataset dataset) {
    1553            XYItemRenderer result = null;
    1554            for (int i = 0; i < this.datasets.size(); i++) {
    1555                if (this.datasets.get(i) == dataset) {
    1556                    result = (XYItemRenderer) this.renderers.get(i);
    1557                    if (result == null) {
    1558                        result = getRenderer();
    1559                    }
    1560                    break;
    1561                }
    1562            }
    1563            return result;
    1564        }
    1565
    1566        /**
    1567         * Returns the weight for this plot when it is used as a subplot within a
    1568         * combined plot.
    1569         *
    1570         * @return The weight.
    1571         *
    1572         * @see #setWeight(int)
    1573         */
    1574        public int getWeight() {
    1575            return this.weight;
    1576        }
    1577
    1578        /**
    1579         * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all
    1580         * registered listeners.
    1581         *
    1582         * @param weight  the weight.
    1583         *
    1584         * @see #getWeight()
    1585         */
    1586        public void setWeight(int weight) {
    1587            this.weight = weight;
    1588            fireChangeEvent();
    1589        }
    1590
    1591        /**
    1592         * Returns <code>true</code> if the domain gridlines are visible, and
    1593         * <code>false<code> otherwise.
    1594         *
    1595         * @return <code>true</code> or <code>false</code>.
    1596         *
    1597         * @see #setDomainGridlinesVisible(boolean)
    1598         */
    1599        public boolean isDomainGridlinesVisible() {
    1600            return this.domainGridlinesVisible;
    1601        }
    1602
    1603        /**
    1604         * Sets the flag that controls whether or not the domain grid-lines are
    1605         * visible.
    1606         * <p>
    1607         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
    1608         * registered listeners.
    1609         *
    1610         * @param visible  the new value of the flag.
    1611         *
    1612         * @see #isDomainGridlinesVisible()
    1613         */
    1614        public void setDomainGridlinesVisible(boolean visible) {
    1615            if (this.domainGridlinesVisible != visible) {
    1616                this.domainGridlinesVisible = visible;
    1617                fireChangeEvent();
    1618            }
    1619        }
    1620
    1621        /**
    1622         * Returns the stroke for the grid-lines (if any) plotted against the
    1623         * domain axis.
    1624         *
    1625         * @return The stroke (never <code>null</code>).
    1626         *
    1627         * @see #setDomainGridlineStroke(Stroke)
    1628         */
    1629        public Stroke getDomainGridlineStroke() {
    1630            return this.domainGridlineStroke;
    1631        }
    1632
    1633        /**
    1634         * Sets the stroke for the grid lines plotted against the domain axis, and
    1635         * sends a {@link PlotChangeEvent} to all registered listeners.
    1636         * <p>
    1637         * If you set this to <code>null</code>, no grid lines will be drawn.
    1638         *
    1639         * @param stroke  the stroke (<code>null</code> not permitted).
    1640         *
    1641         * @throws IllegalArgumentException if <code>stroke</code> is
    1642         *     <code>null</code>.
    1643         *
    1644         * @see #getDomainGridlineStroke()
    1645         */
    1646        public void setDomainGridlineStroke(Stroke stroke) {
    1647            if (stroke == null) {
    1648                throw new IllegalArgumentException("Null 'stroke' argument.");
    1649            }
    1650            this.domainGridlineStroke = stroke;
    1651            fireChangeEvent();
    1652        }
    1653
    1654        /**
    1655         * Returns the paint for the grid lines (if any) plotted against the domain
    1656         * axis.
    1657         *
    1658         * @return The paint (never <code>null</code>).
    1659         *
    1660         * @see #setDomainGridlinePaint(Paint)
    1661         */
    1662        public Paint getDomainGridlinePaint() {
    1663            return this.domainGridlinePaint;
    1664        }
    1665
    1666        /**
    1667         * Sets the paint for the grid lines plotted against the domain axis, and
    1668         * sends a {@link PlotChangeEvent} to all registered listeners.
    1669         *
    1670         * @param paint  the paint (<code>null</code> not permitted).
    1671         *
    1672         * @throws IllegalArgumentException if <code>paint</code> is
    1673         *     <code>null</code>.
    1674         *
    1675         * @see #getDomainGridlinePaint()
    1676         */
    1677        public void setDomainGridlinePaint(Paint paint) {
    1678            if (paint == null) {
    1679                throw new IllegalArgumentException("Null 'paint' argument.");
    1680            }
    1681            this.domainGridlinePaint = paint;
    1682            fireChangeEvent();
    1683        }
    1684
    1685        /**
    1686         * Returns <code>true</code> if the range axis grid is visible, and
    1687         * <code>false<code> otherwise.
    1688         *
    1689         * @return A boolean.
    1690         *
    1691         * @see #setRangeGridlinesVisible(boolean)
    1692         */
    1693        public boolean isRangeGridlinesVisible() {
    1694            return this.rangeGridlinesVisible;
    1695        }
    1696
    1697        /**
    1698         * Sets the flag that controls whether or not the range axis grid lines
    1699         * are visible.
    1700         * <p>
    1701         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
    1702         * registered listeners.
    1703         *
    1704         * @param visible  the new value of the flag.
    1705         *
    1706         * @see #isRangeGridlinesVisible()
    1707         */
    1708        public void setRangeGridlinesVisible(boolean visible) {
    1709            if (this.rangeGridlinesVisible != visible) {
    1710                this.rangeGridlinesVisible = visible;
    1711                fireChangeEvent();
    1712            }
    1713        }
    1714
    1715        /**
    1716         * Returns the stroke for the grid lines (if any) plotted against the
    1717         * range axis.
    1718         *
    1719         * @return The stroke (never <code>null</code>).
    1720         *
    1721         * @see #setRangeGridlineStroke(Stroke)
    1722         */
    1723        public Stroke getRangeGridlineStroke() {
    1724            return this.rangeGridlineStroke;
    1725        }
    1726
    1727        /**
    1728         * Sets the stroke for the grid lines plotted against the range axis,
    1729         * and sends a {@link PlotChangeEvent} to all registered listeners.
    1730         *
    1731         * @param stroke  the stroke (<code>null</code> not permitted).
    1732         *
    1733         * @see #getRangeGridlineStroke()
    1734         */
    1735        public void setRangeGridlineStroke(Stroke stroke) {
    1736            if (stroke == null) {
    1737                throw new IllegalArgumentException("Null 'stroke' argument.");
    1738            }
    1739            this.rangeGridlineStroke = stroke;
    1740            fireChangeEvent();
    1741        }
    1742
    1743        /**
    1744         * Returns the paint for the grid lines (if any) plotted against the range
    1745         * axis.
    1746         *
    1747         * @return The paint (never <code>null</code>).
    1748         *
    1749         * @see #setRangeGridlinePaint(Paint)
    1750         */
    1751        public Paint getRangeGridlinePaint() {
    1752            return this.rangeGridlinePaint;
    1753        }
    1754
    1755        /**
    1756         * Sets the paint for the grid lines plotted against the range axis and
    1757         * sends a {@link PlotChangeEvent} to all registered listeners.
    1758         *
    1759         * @param paint  the paint (<code>null</code> not permitted).
    1760         *
    1761         * @see #getRangeGridlinePaint()
    1762         */
    1763        public void setRangeGridlinePaint(Paint paint) {
    1764            if (paint == null) {
    1765                throw new IllegalArgumentException("Null 'paint' argument.");
    1766            }
    1767            this.rangeGridlinePaint = paint;
    1768            fireChangeEvent();
    1769        }
    1770
    1771        /**
    1772         * Returns a flag that controls whether or not a zero baseline is
    1773         * displayed for the domain axis.
    1774         *
    1775         * @return A boolean.
    1776         *
    1777         * @since 1.0.5
    1778         *
    1779         * @see #setDomainZeroBaselineVisible(boolean)
    1780         */
    1781        public boolean isDomainZeroBaselineVisible() {
    1782            return this.domainZeroBaselineVisible;
    1783        }
    1784
    1785        /**
    1786         * Sets the flag that controls whether or not the zero baseline is
    1787         * displayed for the domain axis, and sends a {@link PlotChangeEvent} to
    1788         * all registered listeners.
    1789         *
    1790         * @param visible  the flag.
    1791         *
    1792         * @since 1.0.5
    1793         *
    1794         * @see #isDomainZeroBaselineVisible()
    1795         */
    1796        public void setDomainZeroBaselineVisible(boolean visible) {
    1797            this.domainZeroBaselineVisible = visible;
    1798            fireChangeEvent();
    1799        }
    1800
    1801        /**
    1802         * Returns the stroke used for the zero baseline against the domain axis.
    1803         *
    1804         * @return The stroke (never <code>null</code>).
    1805         *
    1806         * @since 1.0.5
    1807         *
    1808         * @see #setDomainZeroBaselineStroke(Stroke)
    1809         */
    1810        public Stroke getDomainZeroBaselineStroke() {
    1811            return this.domainZeroBaselineStroke;
    1812        }
    1813
    1814        /**
    1815         * Sets the stroke for the zero baseline for the domain axis,
    1816         * and sends a {@link PlotChangeEvent} to all registered listeners.
    1817         *
    1818         * @param stroke  the stroke (<code>null</code> not permitted).
    1819         *
    1820         * @since 1.0.5
    1821         *
    1822         * @see #getRangeZeroBaselineStroke()
    1823         */
    1824        public void setDomainZeroBaselineStroke(Stroke stroke) {
    1825            if (stroke == null) {
    1826                throw new IllegalArgumentException("Null 'stroke' argument.");
    1827            }
    1828            this.domainZeroBaselineStroke = stroke;
    1829            fireChangeEvent();
    1830        }
    1831
    1832        /**
    1833         * Returns the paint for the zero baseline (if any) plotted against the
    1834         * domain axis.
    1835         *
    1836         * @since 1.0.5
    1837         *
    1838         * @return The paint (never <code>null</code>).
    1839         *
    1840         * @see #setDomainZeroBaselinePaint(Paint)
    1841         */
    1842        public Paint getDomainZeroBaselinePaint() {
    1843            return this.domainZeroBaselinePaint;
    1844        }
    1845
    1846        /**
    1847         * Sets the paint for the zero baseline plotted against the domain axis and
    1848         * sends a {@link PlotChangeEvent} to all registered listeners.
    1849         *
    1850         * @param paint  the paint (<code>null</code> not permitted).
    1851         *
    1852         * @since 1.0.5
    1853         *
    1854         * @see #getDomainZeroBaselinePaint()
    1855         */
    1856        public void setDomainZeroBaselinePaint(Paint paint) {
    1857            if (paint == null) {
    1858                throw new IllegalArgumentException("Null 'paint' argument.");
    1859            }
    1860            this.domainZeroBaselinePaint = paint;
    1861            fireChangeEvent();
    1862        }
    1863
    1864        /**
    1865         * Returns a flag that controls whether or not a zero baseline is
    1866         * displayed for the range axis.
    1867         *
    1868         * @return A boolean.
    1869         *
    1870         * @see #setRangeZeroBaselineVisible(boolean)
    1871         */
    1872        public boolean isRangeZeroBaselineVisible() {
    1873            return this.rangeZeroBaselineVisible;
    1874        }
    1875
    1876        /**
    1877         * Sets the flag that controls whether or not the zero baseline is
    1878         * displayed for the range axis, and sends a {@link PlotChangeEvent} to
    1879         * all registered listeners.
    1880         *
    1881         * @param visible  the flag.
    1882         *
    1883         * @see #isRangeZeroBaselineVisible()
    1884         */
    1885        public void setRangeZeroBaselineVisible(boolean visible) {
    1886            this.rangeZeroBaselineVisible = visible;
    1887            fireChangeEvent();
    1888        }
    1889
    1890        /**
    1891         * Returns the stroke used for the zero baseline against the range axis.
    1892         *
    1893         * @return The stroke (never <code>null</code>).
    1894         *
    1895         * @see #setRangeZeroBaselineStroke(Stroke)
    1896         */
    1897        public Stroke getRangeZeroBaselineStroke() {
    1898            return this.rangeZeroBaselineStroke;
    1899        }
    1900
    1901        /**
    1902         * Sets the stroke for the zero baseline for the range axis,
    1903         * and sends a {@link PlotChangeEvent} to all registered listeners.
    1904         *
    1905         * @param stroke  the stroke (<code>null</code> not permitted).
    1906         *
    1907         * @see #getRangeZeroBaselineStroke()
    1908         */
    1909        public void setRangeZeroBaselineStroke(Stroke stroke) {
    1910            if (stroke == null) {
    1911                throw new IllegalArgumentException("Null 'stroke' argument.");
    1912            }
    1913            this.rangeZeroBaselineStroke = stroke;
    1914            fireChangeEvent();
    1915        }
    1916
    1917        /**
    1918         * Returns the paint for the zero baseline (if any) plotted against the
    1919         * range axis.
    1920         *
    1921         * @return The paint (never <code>null</code>).
    1922         *
    1923         * @see #setRangeZeroBaselinePaint(Paint)
    1924         */
    1925        public Paint getRangeZeroBaselinePaint() {
    1926            return this.rangeZeroBaselinePaint;
    1927        }
    1928
    1929        /**
    1930         * Sets the paint for the zero baseline plotted against the range axis and
    1931         * sends a {@link PlotChangeEvent} to all registered listeners.
    1932         *
    1933         * @param paint  the paint (<code>null</code> not permitted).
    1934         *
    1935         * @see #getRangeZeroBaselinePaint()
    1936         */
    1937        public void setRangeZeroBaselinePaint(Paint paint) {
    1938            if (paint == null) {
    1939                throw new IllegalArgumentException("Null 'paint' argument.");
    1940            }
    1941            this.rangeZeroBaselinePaint = paint;
    1942            fireChangeEvent();
    1943        }
    1944
    1945        /**
    1946         * Returns the paint used for the domain tick bands.  If this is
    1947         * <code>null</code>, no tick bands will be drawn.
    1948         *
    1949         * @return The paint (possibly <code>null</code>).
    1950         *
    1951         * @see #setDomainTickBandPaint(Paint)
    1952         */
    1953        public Paint getDomainTickBandPaint() {
    1954            return this.domainTickBandPaint;
    1955        }
    1956
    1957        /**
    1958         * Sets the paint for the domain tick bands.
    1959         *
    1960         * @param paint  the paint (<code>null</code> permitted).
    1961         *
    1962         * @see #getDomainTickBandPaint()
    1963         */
    1964        public void setDomainTickBandPaint(Paint paint) {
    1965            this.domainTickBandPaint = paint;
    1966            fireChangeEvent();
    1967        }
    1968
    1969        /**
    1970         * Returns the paint used for the range tick bands.  If this is
    1971         * <code>null</code>, no tick bands will be drawn.
    1972         *
    1973         * @return The paint (possibly <code>null</code>).
    1974         *
    1975         * @see #setRangeTickBandPaint(Paint)
    1976         */
    1977        public Paint getRangeTickBandPaint() {
    1978            return this.rangeTickBandPaint;
    1979        }
    1980
    1981        /**
    1982         * Sets the paint for the range tick bands.
    1983         *
    1984         * @param paint  the paint (<code>null</code> permitted).
    1985         *
    1986         * @see #getRangeTickBandPaint()
    1987         */
    1988        public void setRangeTickBandPaint(Paint paint) {
    1989            this.rangeTickBandPaint = paint;
    1990            fireChangeEvent();
    1991        }
    1992
    1993        /**
    1994         * Returns the origin for the quadrants that can be displayed on the plot.
    1995         * This defaults to (0, 0).
    1996         *
    1997         * @return The origin point (never <code>null</code>).
    1998         *
    1999         * @see #setQuadrantOrigin(Point2D)
    2000         */
    2001        public Point2D getQuadrantOrigin() {
    2002            return this.quadrantOrigin;
    2003        }
    2004
    2005        /**
    2006         * Sets the quadrant origin and sends a {@link PlotChangeEvent} to all
    2007         * registered listeners.
    2008         *
    2009         * @param origin  the origin (<code>null</code> not permitted).
    2010         *
    2011         * @see #getQuadrantOrigin()
    2012         */
    2013        public void setQuadrantOrigin(Point2D origin) {
    2014            if (origin == null) {
    2015                throw new IllegalArgumentException("Null 'origin' argument.");
    2016            }
    2017            this.quadrantOrigin = origin;
    2018            fireChangeEvent();
    2019        }
    2020
    2021        /**
    2022         * Returns the paint used for the specified quadrant.
    2023         *
    2024         * @param index  the quadrant index (0-3).
    2025         *
    2026         * @return The paint (possibly <code>null</code>).
    2027         *
    2028         * @see #setQuadrantPaint(int, Paint)
    2029         */
    2030        public Paint getQuadrantPaint(int index) {
    2031            if (index < 0 || index > 3) {
    2032                throw new IllegalArgumentException("The index value (" + index
    2033                        + ") should be in the range 0 to 3.");
    2034            }
    2035            return this.quadrantPaint[index];
    2036        }
    2037
    2038        /**
    2039         * Sets the paint used for the specified quadrant and sends a
    2040         * {@link PlotChangeEvent} to all registered listeners.
    2041         *
    2042         * @param index  the quadrant index (0-3).
    2043         * @param paint  the paint (<code>null</code> permitted).
    2044         *
    2045         * @see #getQuadrantPaint(int)
    2046         */
    2047        public void setQuadrantPaint(int index, Paint paint) {
    2048            if (index < 0 || index > 3) {
    2049                throw new IllegalArgumentException("The index value (" + index
    2050                        + ") should be in the range 0 to 3.");
    2051            }
    2052            this.quadrantPaint[index] = paint;
    2053            fireChangeEvent();
    2054        }
    2055
    2056        /**
    2057         * Adds a marker for the domain axis and sends a {@link PlotChangeEvent}
    2058         * to all registered listeners.
    2059         * <P>
    2060         * Typically a marker will be drawn by the renderer as a line perpendicular
    2061         * to the range axis, however this is entirely up to the renderer.
    2062         *
    2063         * @param marker  the marker (<code>null</code> not permitted).
    2064         *
    2065         * @see #addDomainMarker(Marker, Layer)
    2066         * @see #clearDomainMarkers()
    2067         */
    2068        public void addDomainMarker(Marker marker) {
    2069            // defer argument checking...
    2070            addDomainMarker(marker, Layer.FOREGROUND);
    2071        }
    2072
    2073        /**
    2074         * Adds a marker for the domain axis in the specified layer and sends a
    2075         * {@link PlotChangeEvent} to all registered listeners.
    2076         * <P>
    2077         * Typically a marker will be drawn by the renderer as a line perpendicular
    2078         * to the range axis, however this is entirely up to the renderer.
    2079         *
    2080         * @param marker  the marker (<code>null</code> not permitted).
    2081         * @param layer  the layer (foreground or background).
    2082         *
    2083         * @see #addDomainMarker(int, Marker, Layer)
    2084         */
    2085        public void addDomainMarker(Marker marker, Layer layer) {
    2086            addDomainMarker(0, marker, layer);
    2087        }
    2088
    2089        /**
    2090         * Clears all the (foreground and background) domain markers and sends a
    2091         * {@link PlotChangeEvent} to all registered listeners.
    2092         *
    2093         * @see #addDomainMarker(int, Marker, Layer)
    2094         */
    2095        public void clearDomainMarkers() {
    2096            if (this.backgroundDomainMarkers != null) {
    2097                Set keys = this.backgroundDomainMarkers.keySet();
    2098                Iterator iterator = keys.iterator();
    2099                while (iterator.hasNext()) {
    2100                    Integer key = (Integer) iterator.next();
    2101                    clearDomainMarkers(key.intValue());
    2102                }
    2103                this.backgroundDomainMarkers.clear();
    2104            }
    2105            if (this.foregroundDomainMarkers != null) {
    2106                Set keys = this.foregroundDomainMarkers.keySet();
    2107                Iterator iterator = keys.iterator();
    2108                while (iterator.hasNext()) {
    2109                    Integer key = (Integer) iterator.next();
    2110                    clearDomainMarkers(key.intValue());
    2111                }
    2112                this.foregroundDomainMarkers.clear();
    2113            }
    2114            fireChangeEvent();
    2115        }
    2116
    2117        /**
    2118         * Clears the (foreground and background) domain markers for a particular
    2119         * renderer.
    2120         *
    2121         * @param index  the renderer index.
    2122         *
    2123         * @see #clearRangeMarkers(int)
    2124         */
    2125        public void clearDomainMarkers(int index) {
    2126            Integer key = new Integer(index);
    2127            if (this.backgroundDomainMarkers != null) {
    2128                Collection markers
    2129                    = (Collection) this.backgroundDomainMarkers.get(key);
    2130                if (markers != null) {
    2131                    Iterator iterator = markers.iterator();
    2132                    while (iterator.hasNext()) {
    2133                        Marker m = (Marker) iterator.next();
    2134                        m.removeChangeListener(this);
    2135                    }
    2136                    markers.clear();
    2137                }
    2138            }
    2139            if (this.foregroundRangeMarkers != null) {
    2140                Collection markers
    2141                    = (Collection) this.foregroundDomainMarkers.get(key);
    2142                if (markers != null) {
    2143                    Iterator iterator = markers.iterator();
    2144                    while (iterator.hasNext()) {
    2145                        Marker m = (Marker) iterator.next();
    2146                        m.removeChangeListener(this);
    2147                    }
    2148                    markers.clear();
    2149                }
    2150            }
    2151            fireChangeEvent();
    2152        }
    2153
    2154        /**
    2155         * Adds a marker for a specific dataset/renderer and sends a
    2156         * {@link PlotChangeEvent} to all registered listeners.
    2157         * <P>
    2158         * Typically a marker will be drawn by the renderer as a line perpendicular
    2159         * to the domain axis (that the renderer is mapped to), however this is
    2160         * entirely up to the renderer.
    2161         *
    2162         * @param index  the dataset/renderer index.
    2163         * @param marker  the marker.
    2164         * @param layer  the layer (foreground or background).
    2165         *
    2166         * @see #clearDomainMarkers(int)
    2167         * @see #addRangeMarker(int, Marker, Layer)
    2168         */
    2169        public void addDomainMarker(int index, Marker marker, Layer layer) {
    2170            addDomainMarker(index, marker, layer, true);
    2171        }
    2172
    2173        /**
    2174         * Adds a marker for a specific dataset/renderer and, if requested, sends a
    2175         * {@link PlotChangeEvent} to all registered listeners.
    2176         * <P>
    2177         * Typically a marker will be drawn by the renderer as a line perpendicular
    2178         * to the domain axis (that the renderer is mapped to), however this is
    2179         * entirely up to the renderer.
    2180         *
    2181         * @param index  the dataset/renderer index.
    2182         * @param marker  the marker.
    2183         * @param layer  the layer (foreground or background).
    2184         * @param notify  notify listeners?
    2185         *
    2186         * @since 1.0.10
    2187         */
    2188        public void addDomainMarker(int index, Marker marker, Layer layer,
    2189                boolean notify) {
    2190            if (marker == null) {
    2191                throw new IllegalArgumentException("Null 'marker' not permitted.");
    2192            }
    2193            if (layer == null) {
    2194                throw new IllegalArgumentException("Null 'layer' not permitted.");
    2195            }
    2196            Collection markers;
    2197            if (layer == Layer.FOREGROUND) {
    2198                markers = (Collection) this.foregroundDomainMarkers.get(
    2199                        new Integer(index));
    2200                if (markers == null) {
    2201                    markers = new java.util.ArrayList();
    2202                    this.foregroundDomainMarkers.put(new Integer(index), markers);
    2203                }
    2204                markers.add(marker);
    2205            }
    2206            else if (layer == Layer.BACKGROUND) {
    2207                markers = (Collection) this.backgroundDomainMarkers.get(
    2208                        new Integer(index));
    2209                if (markers == null) {
    2210                    markers = new java.util.ArrayList();
    2211                    this.backgroundDomainMarkers.put(new Integer(index), markers);
    2212                }
    2213                markers.add(marker);
    2214            }
    2215            marker.addChangeListener(this);
    2216            if (notify) {
    2217                fireChangeEvent();
    2218            }
    2219        }
    2220
    2221        /**
    2222         * Removes a marker for the domain axis and sends a {@link PlotChangeEvent}
    2223         * to all registered listeners.
    2224         *
    2225         * @param marker  the marker.
    2226         *
    2227         * @return A boolean indicating whether or not the marker was actually
    2228         *         removed.
    2229         *
    2230         * @since 1.0.7
    2231         */
    2232        public boolean removeDomainMarker(Marker marker) {
    2233            return removeDomainMarker(marker, Layer.FOREGROUND);
    2234        }
    2235
    2236        /**
    2237         * Removes a marker for the domain axis in the specified layer and sends a
    2238         * {@link PlotChangeEvent} to all registered listeners.
    2239         *
    2240         * @param marker the marker (<code>null</code> not permitted).
    2241         * @param layer the layer (foreground or background).
    2242         *
    2243         * @return A boolean indicating whether or not the marker was actually
    2244         *         removed.
    2245         *
    2246         * @since 1.0.7
    2247         */
    2248        public boolean removeDomainMarker(Marker marker, Layer layer) {
    2249            return removeDomainMarker(0, marker, layer);
    2250        }
    2251
    2252        /**
    2253         * Removes a marker for a specific dataset/renderer and sends a
    2254         * {@link PlotChangeEvent} to all registered listeners.
    2255         *
    2256         * @param index the dataset/renderer index.
    2257         * @param marker the marker.
    2258         * @param layer the layer (foreground or background).
    2259         *
    2260         * @return A boolean indicating whether or not the marker was actually
    2261         *         removed.
    2262         *
    2263         * @since 1.0.7
    2264         */
    2265        public boolean removeDomainMarker(int index, Marker marker, Layer layer) {
    2266            return removeDomainMarker(index, marker, layer, true);
    2267        }
    2268
    2269        /**
    2270         * Removes a marker for a specific dataset/renderer and, if requested,
    2271         * sends a {@link PlotChangeEvent} to all registered listeners.
    2272         *
    2273         * @param index  the dataset/renderer index.
    2274         * @param marker  the marker.
    2275         * @param layer  the layer (foreground or background).
    2276         * @param notify  notify listeners?
    2277         *
    2278         * @return A boolean indicating whether or not the marker was actually
    2279         *         removed.
    2280         *
    2281         * @since 1.0.10
    2282         */
    2283        public boolean removeDomainMarker(int index, Marker marker, Layer layer,
    2284                boolean notify) {
    2285            ArrayList markers;
    2286            if (layer == Layer.FOREGROUND) {
    2287                markers = (ArrayList) this.foregroundDomainMarkers.get(
    2288                        new Integer(index));
    2289            }
    2290            else {
    2291                markers = (ArrayList) this.backgroundDomainMarkers.get(
    2292                        new Integer(index));
    2293            }
    2294            if (markers == null) {
    2295                return false;
    2296            }
    2297            boolean removed = markers.remove(marker);
    2298            if (removed && notify) {
    2299                fireChangeEvent();
    2300            }
    2301            return removed;
    2302        }
    2303
    2304        /**
    2305         * Adds a marker for the range axis and sends a {@link PlotChangeEvent} to
    2306         * all registered listeners.
    2307         * <P>
    2308         * Typically a marker will be drawn by the renderer as a line perpendicular
    2309         * to the range axis, however this is entirely up to the renderer.
    2310         *
    2311         * @param marker  the marker (<code>null</code> not permitted).
    2312         *
    2313         * @see #addRangeMarker(Marker, Layer)
    2314         */
    2315        public void addRangeMarker(Marker marker) {
    2316            addRangeMarker(marker, Layer.FOREGROUND);
    2317        }
    2318
    2319        /**
    2320         * Adds a marker for the range axis in the specified layer and sends a
    2321         * {@link PlotChangeEvent} to all registered listeners.
    2322         * <P>
    2323         * Typically a marker will be drawn by the renderer as a line perpendicular
    2324         * to the range axis, however this is entirely up to the renderer.
    2325         *
    2326         * @param marker  the marker (<code>null</code> not permitted).
    2327         * @param layer  the layer (foreground or background).
    2328         *
    2329         * @see #addRangeMarker(int, Marker, Layer)
    2330         */
    2331        public void addRangeMarker(Marker marker, Layer layer) {
    2332            addRangeMarker(0, marker, layer);
    2333        }
    2334
    2335        /**
    2336         * Clears all the range markers and sends a {@link PlotChangeEvent} to all
    2337         * registered listeners.
    2338         *
    2339         * @see #clearRangeMarkers()
    2340         */
    2341        public void clearRangeMarkers() {
    2342            if (this.backgroundRangeMarkers != null) {
    2343                Set keys = this.backgroundRangeMarkers.keySet();
    2344                Iterator iterator = keys.iterator();
    2345                while (iterator.hasNext()) {
    2346                    Integer key = (Integer) iterator.next();
    2347                    clearRangeMarkers(key.intValue());
    2348                }
    2349                this.backgroundRangeMarkers.clear();
    2350            }
    2351            if (this.foregroundRangeMarkers != null) {
    2352                Set keys = this.foregroundRangeMarkers.keySet();
    2353                Iterator iterator = keys.iterator();
    2354                while (iterator.hasNext()) {
    2355                    Integer key = (Integer) iterator.next();
    2356                    clearRangeMarkers(key.intValue());
    2357                }
    2358                this.foregroundRangeMarkers.clear();
    2359            }
    2360            fireChangeEvent();
    2361        }
    2362
    2363        /**
    2364         * Adds a marker for a specific dataset/renderer and sends a
    2365         * {@link PlotChangeEvent} to all registered listeners.
    2366         * <P>
    2367         * Typically a marker will be drawn by the renderer as a line perpendicular
    2368         * to the range axis, however this is entirely up to the renderer.
    2369         *
    2370         * @param index  the dataset/renderer index.
    2371         * @param marker  the marker.
    2372         * @param layer  the layer (foreground or background).
    2373         *
    2374         * @see #clearRangeMarkers(int)
    2375         * @see #addDomainMarker(int, Marker, Layer)
    2376         */
    2377        public void addRangeMarker(int index, Marker marker, Layer layer) {
    2378            addRangeMarker(index, marker, layer, true);
    2379        }
    2380
    2381        /**
    2382         * Adds a marker for a specific dataset/renderer and, if requested, sends a
    2383         * {@link PlotChangeEvent} to all registered listeners.
    2384         * <P>
    2385         * Typically a marker will be drawn by the renderer as a line perpendicular
    2386         * to the range axis, however this is entirely up to the renderer.
    2387         *
    2388         * @param index  the dataset/renderer index.
    2389         * @param marker  the marker.
    2390         * @param layer  the layer (foreground or background).
    2391         * @param notify  notify listeners?
    2392         *
    2393         * @since 1.0.10
    2394         */
    2395        public void addRangeMarker(int index, Marker marker, Layer layer,
    2396                boolean notify) {
    2397            Collection markers;
    2398            if (layer == Layer.FOREGROUND) {
    2399                markers = (Collection) this.foregroundRangeMarkers.get(
    2400                        new Integer(index));
    2401                if (markers == null) {
    2402                    markers = new java.util.ArrayList();
    2403                    this.foregroundRangeMarkers.put(new Integer(index), markers);
    2404                }
    2405                markers.add(marker);
    2406            }
    2407            else if (layer == Layer.BACKGROUND) {
    2408                markers = (Collection) this.backgroundRangeMarkers.get(
    2409                        new Integer(index));
    2410                if (markers == null) {
    2411                    markers = new java.util.ArrayList();
    2412                    this.backgroundRangeMarkers.put(new Integer(index), markers);
    2413                }
    2414                markers.add(marker);
    2415            }
    2416            marker.addChangeListener(this);
    2417            if (notify) {
    2418                fireChangeEvent();
    2419            }
    2420        }
    2421
    2422        /**
    2423         * Clears the (foreground and background) range markers for a particular
    2424         * renderer.
    2425         *
    2426         * @param index  the renderer index.
    2427         */
    2428        public void clearRangeMarkers(int index) {
    2429            Integer key = new Integer(index);
    2430            if (this.backgroundRangeMarkers != null) {
    2431                Collection markers
    2432                    = (Collection) this.backgroundRangeMarkers.get(key);
    2433                if (markers != null) {
    2434                    Iterator iterator = markers.iterator();
    2435                    while (iterator.hasNext()) {
    2436                        Marker m = (Marker) iterator.next();
    2437                        m.removeChangeListener(this);
    2438                    }
    2439                    markers.clear();
    2440                }
    2441            }
    2442            if (this.foregroundRangeMarkers != null) {
    2443                Collection markers
    2444                    = (Collection) this.foregroundRangeMarkers.get(key);
    2445                if (markers != null) {
    2446                    Iterator iterator = markers.iterator();
    2447                    while (iterator.hasNext()) {
    2448                        Marker m = (Marker) iterator.next();
    2449                        m.removeChangeListener(this);
    2450                    }
    2451                    markers.clear();
    2452                }
    2453            }
    2454            fireChangeEvent();
    2455        }
    2456
    2457        /**
    2458         * Removes a marker for the range axis and sends a {@link PlotChangeEvent}
    2459         * to all registered listeners.
    2460         *
    2461         * @param marker the marker.
    2462         *
    2463         * @return A boolean indicating whether or not the marker was actually
    2464         *         removed.
    2465         *
    2466         * @since 1.0.7
    2467         */
    2468        public boolean removeRangeMarker(Marker marker) {
    2469            return removeRangeMarker(marker, Layer.FOREGROUND);
    2470        }
    2471
    2472        /**
    2473         * Removes a marker for the range axis in the specified layer and sends a
    2474         * {@link PlotChangeEvent} to all registered listeners.
    2475         *
    2476         * @param marker the marker (<code>null</code> not permitted).
    2477         * @param layer the layer (foreground or background).
    2478         *
    2479         * @return A boolean indicating whether or not the marker was actually
    2480         *         removed.
    2481         *
    2482         * @since 1.0.7
    2483         */
    2484        public boolean removeRangeMarker(Marker marker, Layer layer) {
    2485            return removeRangeMarker(0, marker, layer);
    2486        }
    2487
    2488        /**
    2489         * Removes a marker for a specific dataset/renderer and sends a
    2490         * {@link PlotChangeEvent} to all registered listeners.
    2491         *
    2492         * @param index the dataset/renderer index.
    2493         * @param marker the marker.
    2494         * @param layer the layer (foreground or background).
    2495         *
    2496         * @return A boolean indicating whether or not the marker was actually
    2497         *         removed.
    2498         *
    2499         * @since 1.0.7
    2500         */
    2501        public boolean removeRangeMarker(int index, Marker marker, Layer layer) {
    2502            return removeRangeMarker(index, marker, layer, true);
    2503        }
    2504
    2505        /**
    2506         * Removes a marker for a specific dataset/renderer and sends a
    2507         * {@link PlotChangeEvent} to all registered listeners.
    2508         *
    2509         * @param index  the dataset/renderer index.
    2510         * @param marker  the marker.
    2511         * @param layer  the layer (foreground or background).
    2512         * @param notify  notify listeners?
    2513         *
    2514         * @return A boolean indicating whether or not the marker was actually
    2515         *         removed.
    2516         *
    2517         * @since 1.0.10
    2518         */
    2519        public boolean removeRangeMarker(int index, Marker marker, Layer layer,
    2520                boolean notify) {
    2521            if (marker == null) {
    2522                throw new IllegalArgumentException("Null 'marker' argument.");
    2523            }
    2524            ArrayList markers;
    2525            if (layer == Layer.FOREGROUND) {
    2526                markers = (ArrayList) this.foregroundRangeMarkers.get(
    2527                        new Integer(index));
    2528            }
    2529            else {
    2530                markers = (ArrayList) this.backgroundRangeMarkers.get(
    2531                        new Integer(index));
    2532            }
    2533            if (markers == null) {
    2534                return false;
    2535            }
    2536            boolean removed = markers.remove(marker);
    2537            if (removed && notify) {
    2538                fireChangeEvent();
    2539            }
    2540            return removed;
    2541        }
    2542
    2543        /**
    2544         * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to
    2545         * all registered listeners.
    2546         *
    2547         * @param annotation  the annotation (<code>null</code> not permitted).
    2548         *
    2549         * @see #getAnnotations()
    2550         * @see #removeAnnotation(XYAnnotation)
    2551         */
    2552        public void addAnnotation(XYAnnotation annotation) {
    2553            addAnnotation(annotation, true);
    2554        }
    2555
    2556        /**
    2557         * Adds an annotation to the plot and, if requested, sends a
    2558         * {@link PlotChangeEvent} to all registered listeners.
    2559         *
    2560         * @param annotation  the annotation (<code>null</code> not permitted).
    2561         * @param notify  notify listeners?
    2562         *
    2563         * @since 1.0.10
    2564         */
    2565        public void addAnnotation(XYAnnotation annotation, boolean notify) {
    2566            if (annotation == null) {
    2567                throw new IllegalArgumentException("Null 'annotation' argument.");
    2568            }
    2569            this.annotations.add(annotation);
    2570            if (notify) {
    2571                fireChangeEvent();
    2572            }
    2573        }
    2574
    2575        /**
    2576         * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
    2577         * to all registered listeners.
    2578         *
    2579         * @param annotation  the annotation (<code>null</code> not permitted).
    2580         *
    2581         * @return A boolean (indicates whether or not the annotation was removed).
    2582         *
    2583         * @see #addAnnotation(XYAnnotation)
    2584         * @see #getAnnotations()
    2585         */
    2586        public boolean removeAnnotation(XYAnnotation annotation) {
    2587            return removeAnnotation(annotation, true);
    2588        }
    2589
    2590        /**
    2591         * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
    2592         * to all registered listeners.
    2593         *
    2594         * @param annotation  the annotation (<code>null</code> not permitted).
    2595         * @param notify  notify listeners?
    2596         *
    2597         * @return A boolean (indicates whether or not the annotation was removed).
    2598         *
    2599         * @since 1.0.10
    2600         */
    2601        public boolean removeAnnotation(XYAnnotation annotation, boolean notify) {
    2602            if (annotation == null) {
    2603                throw new IllegalArgumentException("Null 'annotation' argument.");
    2604            }
    2605            boolean removed = this.annotations.remove(annotation);
    2606            if (removed && notify) {
    2607                fireChangeEvent();
    2608            }
    2609            return removed;
    2610        }
    2611
    2612        /**
    2613         * Returns the list of annotations.
    2614         *
    2615         * @return The list of annotations.
    2616         *
    2617         * @since 1.0.1
    2618         *
    2619         * @see #addAnnotation(XYAnnotation)
    2620         */
    2621        public List getAnnotations() {
    2622            return new ArrayList(this.annotations);
    2623        }
    2624
    2625        /**
    2626         * Clears all the annotations and sends a {@link PlotChangeEvent} to all
    2627         * registered listeners.
    2628         *
    2629         * @see #addAnnotation(XYAnnotation)
    2630         */
    2631        public void clearAnnotations() {
    2632            this.annotations.clear();
    2633            fireChangeEvent();
    2634        }
    2635
    2636        /**
    2637         * Calculates the space required for all the axes in the plot.
    2638         *
    2639         * @param g2  the graphics device.
    2640         * @param plotArea  the plot area.
    2641         *
    2642         * @return The required space.
    2643         */
    2644        protected AxisSpace calculateAxisSpace(Graphics2D g2,
    2645                                               Rectangle2D plotArea) {
    2646            AxisSpace space = new AxisSpace();
    2647            space = calculateRangeAxisSpace(g2, plotArea, space);
    2648            Rectangle2D revPlotArea = space.shrink(plotArea, null);
    2649            space = calculateDomainAxisSpace(g2, revPlotArea, space);
    2650            return space;
    2651        }
    2652
    2653        /**
    2654         * Calculates the space required for the domain axis/axes.
    2655         *
    2656         * @param g2  the graphics device.
    2657         * @param plotArea  the plot area.
    2658         * @param space  a carrier for the result (<code>null</code> permitted).
    2659         *
    2660         * @return The required space.
    2661         */
    2662        protected AxisSpace calculateDomainAxisSpace(Graphics2D g2,
    2663                                                     Rectangle2D plotArea,
    2664                                                     AxisSpace space) {
    2665
    2666            if (space == null) {
    2667                space = new AxisSpace();
    2668            }
    2669
    2670            // reserve some space for the domain axis...
    2671            if (this.fixedDomainAxisSpace != null) {
    2672                if (this.orientation == PlotOrientation.HORIZONTAL) {
    2673                    space.ensureAtLeast(this.fixedDomainAxisSpace.getLeft(),
    2674                            RectangleEdge.LEFT);
    2675                    space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(),
    2676                            RectangleEdge.RIGHT);
    2677                }
    2678                else if (this.orientation == PlotOrientation.VERTICAL) {
    2679                    space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(),
    2680                            RectangleEdge.TOP);
    2681                    space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(),
    2682                            RectangleEdge.BOTTOM);
    2683                }
    2684            }
    2685            else {
    2686                // reserve space for the domain axes...
    2687                for (int i = 0; i < this.domainAxes.size(); i++) {
    2688                    Axis axis = (Axis) this.domainAxes.get(i);
    2689                    if (axis != null) {
    2690                        RectangleEdge edge = getDomainAxisEdge(i);
    2691                        space = axis.reserveSpace(g2, this, plotArea, edge, space);
    2692                    }
    2693                }
    2694            }
    2695
    2696            return space;
    2697
    2698        }
    2699
    2700        /**
    2701         * Calculates the space required for the range axis/axes.
    2702         *
    2703         * @param g2  the graphics device.
    2704         * @param plotArea  the plot area.
    2705         * @param space  a carrier for the result (<code>null</code> permitted).
    2706         *
    2707         * @return The required space.
    2708         */
    2709        protected AxisSpace calculateRangeAxisSpace(Graphics2D g2,
    2710                                                    Rectangle2D plotArea,
    2711                                                    AxisSpace space) {
    2712
    2713            if (space == null) {
    2714                space = new AxisSpace();
    2715            }
    2716
    2717            // reserve some space for the range axis...
    2718            if (this.fixedRangeAxisSpace != null) {
    2719                if (this.orientation == PlotOrientation.HORIZONTAL) {
    2720                    space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(),
    2721                            RectangleEdge.TOP);
    2722                    space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(),
    2723                            RectangleEdge.BOTTOM);
    2724                }
    2725                else if (this.orientation == PlotOrientation.VERTICAL) {
    2726                    space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(),
    2727                            RectangleEdge.LEFT);
    2728                    space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(),
    2729                            RectangleEdge.RIGHT);
    2730                }
    2731            }
    2732            else {
    2733                // reserve space for the range axes...
    2734                for (int i = 0; i < this.rangeAxes.size(); i++) {
    2735                    Axis axis = (Axis) this.rangeAxes.get(i);
    2736                    if (axis != null) {
    2737                        RectangleEdge edge = getRangeAxisEdge(i);
    2738                        space = axis.reserveSpace(g2, this, plotArea, edge, space);
    2739                    }
    2740                }
    2741            }
    2742            return space;
    2743
    2744        }
    2745
    2746        /**
    2747         * Draws the plot within the specified area on a graphics device.
    2748         *
    2749         * @param g2  the graphics device.
    2750         * @param area  the plot area (in Java2D space).
    2751         * @param anchor  an anchor point in Java2D space (<code>null</code>
    2752         *                permitted).
    2753         * @param parentState  the state from the parent plot, if there is one
    2754         *                     (<code>null</code> permitted).
    2755         * @param info  collects chart drawing information (<code>null</code>
    2756         *              permitted).
    2757         */
    2758        public void draw(Graphics2D g2,
    2759                         Rectangle2D area,
    2760                         Point2D anchor,
    2761                         PlotState parentState,
    2762                         PlotRenderingInfo info) {
    2763
    2764            // if the plot area is too small, just return...
    2765            boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
    2766            boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
    2767            if (b1 || b2) {
    2768                return;
    2769            }
    2770
    2771            // record the plot area...
    2772            if (info != null) {
    2773                info.setPlotArea(area);
    2774            }
    2775
    2776            // adjust the drawing area for the plot insets (if any)...
    2777            RectangleInsets insets = getInsets();
    2778            insets.trim(area);
    2779
    2780            AxisSpace space = calculateAxisSpace(g2, area);
    2781            Rectangle2D dataArea = space.shrink(area, null);
    2782            this.axisOffset.trim(dataArea);
    2783
    2784            if (info != null) {
    2785                info.setDataArea(dataArea);
    2786            }
    2787
    2788            // draw the plot background and axes...
    2789            drawBackground(g2, dataArea);
    2790            Map axisStateMap = drawAxes(g2, area, dataArea, info);
    2791
    2792            PlotOrientation orient = getOrientation();
    2793
    2794            // the anchor point is typically the point where the mouse last
    2795            // clicked - the crosshairs will be driven off this point...
    2796            if (anchor != null && !dataArea.contains(anchor)) {
    2797                anchor = null;
    2798            }
    2799            CrosshairState crosshairState = new CrosshairState();
    2800            crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
    2801            crosshairState.setAnchor(anchor);
    2802
    2803            crosshairState.setAnchorX(Double.NaN);
    2804            crosshairState.setAnchorY(Double.NaN);
    2805            if (anchor != null) {
    2806                ValueAxis domainAxis = getDomainAxis();
    2807                if (domainAxis != null) {
    2808                    double x;
    2809                    if (orient == PlotOrientation.VERTICAL) {
    2810                        x = domainAxis.java2DToValue(anchor.getX(), dataArea,
    2811                                getDomainAxisEdge());
    2812                    }
    2813                    else {
    2814                        x = domainAxis.java2DToValue(anchor.getY(), dataArea,
    2815                                getDomainAxisEdge());
    2816                    }
    2817                    crosshairState.setAnchorX(x);
    2818                }
    2819                ValueAxis rangeAxis = getRangeAxis();
    2820                if (rangeAxis != null) {
    2821                    double y;
    2822                    if (orient == PlotOrientation.VERTICAL) {
    2823                        y = rangeAxis.java2DToValue(anchor.getY(), dataArea,
    2824                                getRangeAxisEdge());
    2825                    }
    2826                    else {
    2827                        y = rangeAxis.java2DToValue(anchor.getX(), dataArea,
    2828                                getRangeAxisEdge());
    2829                    }
    2830                    crosshairState.setAnchorY(y);
    2831                }
    2832            }
    2833            crosshairState.setCrosshairX(getDomainCrosshairValue());
    2834            crosshairState.setCrosshairY(getRangeCrosshairValue());
    2835            Shape originalClip = g2.getClip();
    2836            Composite originalComposite = g2.getComposite();
    2837
    2838            g2.clip(dataArea);
    2839            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    2840                    getForegroundAlpha()));
    2841
    2842            AxisState domainAxisState = (AxisState) axisStateMap.get(
    2843                    getDomainAxis());
    2844            if (domainAxisState == null) {
    2845                if (parentState != null) {
    2846                    domainAxisState = (AxisState) parentState.getSharedAxisStates()
    2847                            .get(getDomainAxis());
    2848                }
    2849            }
    2850
    2851            AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis());
    2852            if (rangeAxisState == null) {
    2853                if (parentState != null) {
    2854                    rangeAxisState = (AxisState) parentState.getSharedAxisStates()
    2855                            .get(getRangeAxis());
    2856                }
    2857            }
    2858            if (domainAxisState != null) {
    2859                drawDomainTickBands(g2, dataArea, domainAxisState.getTicks());
    2860            }
    2861            if (rangeAxisState != null) {
    2862                drawRangeTickBands(g2, dataArea, rangeAxisState.getTicks());
    2863            }
    2864            if (domainAxisState != null) {
    2865                drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
    2866                drawZeroDomainBaseline(g2, dataArea);
    2867            }
    2868            if (rangeAxisState != null) {
    2869                drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
    2870                drawZeroRangeBaseline(g2, dataArea);
    2871            }
    2872
    2873            // draw the markers that are associated with a specific renderer...
    2874            for (int i = 0; i < this.renderers.size(); i++) {
    2875                drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND);
    2876            }
    2877            for (int i = 0; i < this.renderers.size(); i++) {
    2878                drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND);
    2879            }
    2880
    2881            // now draw annotations and render data items...
    2882            boolean foundData = false;
    2883            DatasetRenderingOrder order = getDatasetRenderingOrder();
    2884            if (order == DatasetRenderingOrder.FORWARD) {
    2885
    2886                // draw background annotations
    2887                int rendererCount = this.renderers.size();
    2888                for (int i = 0; i < rendererCount; i++) {
    2889                    XYItemRenderer r = getRenderer(i);
    2890                    if (r != null) {
    2891                        ValueAxis domainAxis = getDomainAxisForDataset(i);
    2892                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
    2893                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
    2894                                Layer.BACKGROUND, info);
    2895                    }
    2896                }
    2897
    2898                // render data items...
    2899                for (int i = 0; i < getDatasetCount(); i++) {
    2900                    foundData = render(g2, dataArea, i, info, crosshairState)
    2901                        || foundData;
    2902                }
    2903
    2904                // draw foreground annotations
    2905                for (int i = 0; i < rendererCount; i++) {
    2906                    XYItemRenderer r = getRenderer(i);
    2907                    if (r != null) {
    2908                        ValueAxis domainAxis = getDomainAxisForDataset(i);
    2909                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
    2910                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
    2911                                Layer.FOREGROUND, info);
    2912                    }
    2913                }
    2914
    2915            }
    2916            else if (order == DatasetRenderingOrder.REVERSE) {
    2917
    2918                // draw background annotations
    2919                int rendererCount = this.renderers.size();
    2920                for (int i = rendererCount - 1; i >= 0; i--) {
    2921                    XYItemRenderer r = getRenderer(i);
    2922                    if (i >= getDatasetCount()) { // we need the dataset to make
    2923                        continue;                 // a link to the axes
    2924                    }
    2925                    if (r != null) {
    2926                        ValueAxis domainAxis = getDomainAxisForDataset(i);
    2927                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
    2928                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
    2929                                Layer.BACKGROUND, info);
    2930                    }
    2931                }
    2932
    2933                for (int i = getDatasetCount() - 1; i >= 0; i--) {
    2934                    foundData = render(g2, dataArea, i, info, crosshairState)
    2935                        || foundData;
    2936                }
    2937
    2938                // draw foreground annotations
    2939                for (int i = rendererCount - 1; i >= 0; i--) {
    2940                    XYItemRenderer r = getRenderer(i);
    2941                    if (i >= getDatasetCount()) { // we need the dataset to make
    2942                        continue;                 // a link to the axes
    2943                    }
    2944                    if (r != null) {
    2945                        ValueAxis domainAxis = getDomainAxisForDataset(i);
    2946                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
    2947                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
    2948                                Layer.FOREGROUND, info);
    2949                    }
    2950                }
    2951
    2952            }
    2953
    2954            // draw domain crosshair if required...
    2955            int xAxisIndex = crosshairState.getDomainAxisIndex();
    2956            ValueAxis xAxis = getDomainAxis(xAxisIndex);
    2957            RectangleEdge xAxisEdge = getDomainAxisEdge(xAxisIndex);
    2958            if (!this.domainCrosshairLockedOnData && anchor != null) {
    2959                double xx;
    2960                if (orient == PlotOrientation.VERTICAL) {
    2961                    xx = xAxis.java2DToValue(anchor.getX(), dataArea, xAxisEdge);
    2962                }
    2963                else {
    2964                    xx = xAxis.java2DToValue(anchor.getY(), dataArea, xAxisEdge);
    2965                }
    2966                crosshairState.setCrosshairX(xx);
    2967            }
    2968            setDomainCrosshairValue(crosshairState.getCrosshairX(), false);
    2969            if (isDomainCrosshairVisible()) {
    2970                double x = getDomainCrosshairValue();
    2971                Paint paint = getDomainCrosshairPaint();
    2972                Stroke stroke = getDomainCrosshairStroke();
    2973                drawDomainCrosshair(g2, dataArea, orient, x, xAxis, stroke, paint);
    2974            }
    2975
    2976            // draw range crosshair if required...
    2977            int yAxisIndex = crosshairState.getRangeAxisIndex();
    2978            ValueAxis yAxis = getRangeAxis(yAxisIndex);
    2979            RectangleEdge yAxisEdge = getRangeAxisEdge(yAxisIndex);
    2980            if (!this.rangeCrosshairLockedOnData && anchor != null) {
    2981                double yy;
    2982                if (orient == PlotOrientation.VERTICAL) {
    2983                    yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge);
    2984                } else {
    2985                    yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge);
    2986                }
    2987                crosshairState.setCrosshairY(yy);
    2988            }
    2989            setRangeCrosshairValue(crosshairState.getCrosshairY(), false);
    2990            if (isRangeCrosshairVisible()) {
    2991                double y = getRangeCrosshairValue();
    2992                Paint paint = getRangeCrosshairPaint();
    2993                Stroke stroke = getRangeCrosshairStroke();
    2994                drawRangeCrosshair(g2, dataArea, orient, y, yAxis, stroke, paint);
    2995            }
    2996
    2997            if (!foundData) {
    2998                drawNoDataMessage(g2, dataArea);
    2999            }
    3000
    3001            for (int i = 0; i < this.renderers.size(); i++) {
    3002                drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND);
    3003            }
    3004            for (int i = 0; i < this.renderers.size(); i++) {
    3005                drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND);
    3006            }
    3007
    3008            drawAnnotations(g2, dataArea, info);
    3009            g2.setClip(originalClip);
    3010            g2.setComposite(originalComposite);
    3011
    3012            drawOutline(g2, dataArea);
    3013
    3014        }
    3015
    3016        /**
    3017         * Draws the background for the plot.
    3018         *
    3019         * @param g2  the graphics device.
    3020         * @param area  the area.
    3021         */
    3022        public void drawBackground(Graphics2D g2, Rectangle2D area) {
    3023            fillBackground(g2, area, this.orientation);
    3024            drawQuadrants(g2, area);
    3025            drawBackgroundImage(g2, area);
    3026        }
    3027
    3028        /**
    3029         * Draws the quadrants.
    3030         *
    3031         * @param g2  the graphics device.
    3032         * @param area  the area.
    3033         *
    3034         * @see #setQuadrantOrigin(Point2D)
    3035         * @see #setQuadrantPaint(int, Paint)
    3036         */
    3037        protected void drawQuadrants(Graphics2D g2, Rectangle2D area) {
    3038            //  0 | 1
    3039            //  --+--
    3040            //  2 | 3
    3041            boolean somethingToDraw = false;
    3042
    3043            ValueAxis xAxis = getDomainAxis();
    3044            double x = xAxis.getRange().constrain(this.quadrantOrigin.getX());
    3045            double xx = xAxis.valueToJava2D(x, area, getDomainAxisEdge());
    3046
    3047            ValueAxis yAxis = getRangeAxis();
    3048            double y = yAxis.getRange().constrain(this.quadrantOrigin.getY());
    3049            double yy = yAxis.valueToJava2D(y, area, getRangeAxisEdge());
    3050
    3051            double xmin = xAxis.getLowerBound();
    3052            double xxmin = xAxis.valueToJava2D(xmin, area, getDomainAxisEdge());
    3053
    3054            double xmax = xAxis.getUpperBound();
    3055            double xxmax = xAxis.valueToJava2D(xmax, area, getDomainAxisEdge());
    3056
    3057            double ymin = yAxis.getLowerBound();
    3058            double yymin = yAxis.valueToJava2D(ymin, area, getRangeAxisEdge());
    3059
    3060            double ymax = yAxis.getUpperBound();
    3061            double yymax = yAxis.valueToJava2D(ymax, area, getRangeAxisEdge());
    3062
    3063            Rectangle2D[] r = new Rectangle2D[] {null, null, null, null};
    3064            if (this.quadrantPaint[0] != null) {
    3065                if (x > xmin && y < ymax) {
    3066                    if (this.orientation == PlotOrientation.HORIZONTAL) {
    3067                        r[0] = new Rectangle2D.Double(Math.min(yymax, yy),
    3068                                Math.min(xxmin, xx), Math.abs(yy - yymax),
    3069                                Math.abs(xx - xxmin)
    3070                        );
    3071                    }
    3072                    else {  // PlotOrientation.VERTICAL
    3073                        r[0] = new Rectangle2D.Double(Math.min(xxmin, xx),
    3074                                Math.min(yymax, yy), Math.abs(xx - xxmin),
    3075                                Math.abs(yy - yymax));
    3076                    }
    3077                    somethingToDraw = true;
    3078                }
    3079            }
    3080            if (this.quadrantPaint[1] != null) {
    3081                if (x < xmax && y < ymax) {
    3082                    if (this.orientation == PlotOrientation.HORIZONTAL) {
    3083                        r[1] = new Rectangle2D.Double(Math.min(yymax, yy),
    3084                                Math.min(xxmax, xx), Math.abs(yy - yymax),
    3085                                Math.abs(xx - xxmax));
    3086                    }
    3087                    else {  // PlotOrientation.VERTICAL
    3088                        r[1] = new Rectangle2D.Double(Math.min(xx, xxmax),
    3089                                Math.min(yymax, yy), Math.abs(xx - xxmax),
    3090                                Math.abs(yy - yymax));
    3091                    }
    3092                    somethingToDraw = true;
    3093                }
    3094            }
    3095            if (this.quadrantPaint[2] != null) {
    3096                if (x > xmin && y > ymin) {
    3097                    if (this.orientation == PlotOrientation.HORIZONTAL) {
    3098                        r[2] = new Rectangle2D.Double(Math.min(yymin, yy),
    3099                                Math.min(xxmin, xx), Math.abs(yy - yymin),
    3100                                Math.abs(xx - xxmin));
    3101                    }
    3102                    else {  // PlotOrientation.VERTICAL
    3103                        r[2] = new Rectangle2D.Double(Math.min(xxmin, xx),
    3104                                Math.min(yymin, yy), Math.abs(xx - xxmin),
    3105                                Math.abs(yy - yymin));
    3106                    }
    3107                    somethingToDraw = true;
    3108                }
    3109            }
    3110            if (this.quadrantPaint[3] != null) {
    3111                if (x < xmax && y > ymin) {
    3112                    if (this.orientation == PlotOrientation.HORIZONTAL) {
    3113                        r[3] = new Rectangle2D.Double(Math.min(yymin, yy),
    3114                                Math.min(xxmax, xx), Math.abs(yy - yymin),
    3115                                Math.abs(xx - xxmax));
    3116                    }
    3117                    else {  // PlotOrientation.VERTICAL
    3118                        r[3] = new Rectangle2D.Double(Math.min(xx, xxmax),
    3119                                Math.min(yymin, yy), Math.abs(xx - xxmax),
    3120                                Math.abs(yy - yymin));
    3121                    }
    3122                    somethingToDraw = true;
    3123                }
    3124            }
    3125            if (somethingToDraw) {
    3126                Composite originalComposite = g2.getComposite();
    3127                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
    3128                        getBackgroundAlpha()));
    3129                for (int i = 0; i < 4; i++) {
    3130                    if (this.quadrantPaint[i] != null && r[i] != null) {
    3131                        g2.setPaint(this.quadrantPaint[i]);
    3132                        g2.fill(r[i]);
    3133                    }
    3134                }
    3135                g2.setComposite(originalComposite);
    3136            }
    3137        }
    3138
    3139        /**
    3140         * Draws the domain tick bands, if any.
    3141         *
    3142         * @param g2  the graphics device.
    3143         * @param dataArea  the data area.
    3144         * @param ticks  the ticks.
    3145         *
    3146         * @see #setDomainTickBandPaint(Paint)
    3147         */
    3148        public void drawDomainTickBands(Graphics2D g2, Rectangle2D dataArea,
    3149                                        List ticks) {
    3150            Paint bandPaint = getDomainTickBandPaint();
    3151            if (bandPaint != null) {
    3152                boolean fillBand = false;
    3153                ValueAxis xAxis = getDomainAxis();
    3154                double previous = xAxis.getLowerBound();
    3155                Iterator iterator = ticks.iterator();
    3156                while (iterator.hasNext()) {
    3157                    ValueTick tick = (ValueTick) iterator.next();
    3158                    double current = tick.getValue();
    3159                    if (fillBand) {
    3160                        getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea,
    3161                                previous, current);
    3162                    }
    3163                    previous = current;
    3164                    fillBand = !fillBand;
    3165                }
    3166                double end = xAxis.getUpperBound();
    3167                if (fillBand) {
    3168                    getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea,
    3169                            previous, end);
    3170                }
    3171            }
    3172        }
    3173
    3174        /**
    3175         * Draws the range tick bands, if any.
    3176         *
    3177         * @param g2  the graphics device.
    3178         * @param dataArea  the data area.
    3179         * @param ticks  the ticks.
    3180         *
    3181         * @see #setRangeTickBandPaint(Paint)
    3182         */
    3183        public void drawRangeTickBands(Graphics2D g2, Rectangle2D dataArea,
    3184                                       List ticks) {
    3185            Paint bandPaint = getRangeTickBandPaint();
    3186            if (bandPaint != null) {
    3187                boolean fillBand = false;
    3188                ValueAxis axis = getRangeAxis();
    3189                double previous = axis.getLowerBound();
    3190                Iterator iterator = ticks.iterator();
    3191                while (iterator.hasNext()) {
    3192                    ValueTick tick = (ValueTick) iterator.next();
    3193                    double current = tick.getValue();
    3194                    if (fillBand) {
    3195                        getRenderer().fillRangeGridBand(g2, this, axis, dataArea,
    3196                                previous, current);
    3197                    }
    3198                    previous = current;
    3199                    fillBand = !fillBand;
    3200                }
    3201                double end = axis.getUpperBound();
    3202                if (fillBand) {
    3203                    getRenderer().fillRangeGridBand(g2, this, axis, dataArea,
    3204                            previous, end);
    3205                }
    3206            }
    3207        }
    3208
    3209        /**
    3210         * A utility method for drawing the axes.
    3211         *
    3212         * @param g2  the graphics device (<code>null</code> not permitted).
    3213         * @param plotArea  the plot area (<code>null</code> not permitted).
    3214         * @param dataArea  the data area (<code>null</code> not permitted).
    3215         * @param plotState  collects information about the plot (<code>null</code>
    3216         *                   permitted).
    3217         *
    3218         * @return A map containing the state for each axis drawn.
    3219         */
    3220        protected Map drawAxes(Graphics2D g2,
    3221                               Rectangle2D plotArea,
    3222                               Rectangle2D dataArea,
    3223                               PlotRenderingInfo plotState) {
    3224
    3225            AxisCollection axisCollection = new AxisCollection();
    3226
    3227            // add domain axes to lists...
    3228            for (int index = 0; index < this.domainAxes.size(); index++) {
    3229                ValueAxis axis = (ValueAxis) this.domainAxes.get(index);
    3230                if (axis != null) {
    3231                    axisCollection.add(axis, getDomainAxisEdge(index));
    3232                }
    3233            }
    3234
    3235            // add range axes to lists...
    3236            for (int index = 0; index < this.rangeAxes.size(); index++) {
    3237                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
    3238                if (yAxis != null) {
    3239                    axisCollection.add(yAxis, getRangeAxisEdge(index));
    3240                }
    3241            }
    3242
    3243            Map axisStateMap = new HashMap();
    3244
    3245            // draw the top axes
    3246            double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
    3247                    dataArea.getHeight());
    3248            Iterator iterator = axisCollection.getAxesAtTop().iterator();
    3249            while (iterator.hasNext()) {
    3250                ValueAxis axis = (ValueAxis) iterator.next();
    3251                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
    3252                        RectangleEdge.TOP, plotState);
    3253                cursor = info.getCursor();
    3254                axisStateMap.put(axis, info);
    3255            }
    3256
    3257            // draw the bottom axes
    3258            cursor = dataArea.getMaxY()
    3259                     + this.axisOffset.calculateBottomOutset(dataArea.getHeight());
    3260            iterator = axisCollection.getAxesAtBottom().iterator();
    3261            while (iterator.hasNext()) {
    3262                ValueAxis axis = (ValueAxis) iterator.next();
    3263                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
    3264                        RectangleEdge.BOTTOM, plotState);
    3265                cursor = info.getCursor();
    3266                axisStateMap.put(axis, info);
    3267            }
    3268
    3269            // draw the left axes
    3270            cursor = dataArea.getMinX()
    3271                     - this.axisOffset.calculateLeftOutset(dataArea.getWidth());
    3272            iterator = axisCollection.getAxesAtLeft().iterator();
    3273            while (iterator.hasNext()) {
    3274                ValueAxis axis = (ValueAxis) iterator.next();
    3275                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
    3276                        RectangleEdge.LEFT, plotState);
    3277                cursor = info.getCursor();
    3278                axisStateMap.put(axis, info);
    3279            }
    3280
    3281            // draw the right axes
    3282            cursor = dataArea.getMaxX()
    3283                     + this.axisOffset.calculateRightOutset(dataArea.getWidth());
    3284            iterator = axisCollection.getAxesAtRight().iterator();
    3285            while (iterator.hasNext()) {
    3286                ValueAxis axis = (ValueAxis) iterator.next();
    3287                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
    3288                        RectangleEdge.RIGHT, plotState);
    3289                cursor = info.getCursor();
    3290                axisStateMap.put(axis, info);
    3291            }
    3292
    3293            return axisStateMap;
    3294        }
    3295
    3296        /**
    3297         * Draws a representation of the data within the dataArea region, using the
    3298         * current renderer.
    3299         * <P>
    3300         * The <code>info</code> and <code>crosshairState</code> arguments may be
    3301         * <code>null</code>.
    3302         *
    3303         * @param g2  the graphics device.
    3304         * @param dataArea  the region in which the data is to be drawn.
    3305         * @param index  the dataset index.
    3306         * @param info  an optional object for collection dimension information.
    3307         * @param crosshairState  collects crosshair information
    3308         *                        (<code>null</code> permitted).
    3309         *
    3310         * @return A flag that indicates whether any data was actually rendered.
    3311         */
    3312        public boolean render(Graphics2D g2, Rectangle2D dataArea, int index,
    3313                PlotRenderingInfo info, CrosshairState crosshairState) {
    3314
    3315            boolean foundData = false;
    3316            XYDataset dataset = getDataset(index);
    3317            if (!DatasetUtilities.isEmptyOrNull(dataset)) {
    3318                foundData = true;
    3319                ValueAxis xAxis = getDomainAxisForDataset(index);
    3320                ValueAxis yAxis = getRangeAxisForDataset(index);
    3321                XYItemRenderer renderer = getRenderer(index);
    3322                if (renderer == null) {
    3323                    renderer = getRenderer();
    3324                    if (renderer == null) { // no default renderer available
    3325                        return foundData;
    3326                    }
    3327                }
    3328
    3329                XYItemRendererState state = renderer.initialise(g2, dataArea, this,
    3330                        dataset, info);
    3331                int passCount = renderer.getPassCount();
    3332
    3333                SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();
    3334                if (seriesOrder == SeriesRenderingOrder.REVERSE) {
    3335                    //render series in reverse order
    3336                    for (int pass = 0; pass < passCount; pass++) {
    3337                        int seriesCount = dataset.getSeriesCount();
    3338                        for (int series = seriesCount - 1; series >= 0; series--) {
    3339                            int firstItem = 0;
    3340                            int lastItem = dataset.getItemCount(series) - 1;
    3341                            if (lastItem == -1) {
    3342                                continue;
    3343                            }
    3344                            if (state.getProcessVisibleItemsOnly()) {
    3345                                int[] itemBounds = RendererUtilities.findLiveItems(
    3346                                        dataset, series, xAxis.getLowerBound(),
    3347                                        xAxis.getUpperBound());
    3348                                firstItem = itemBounds[0];
    3349                                lastItem = itemBounds[1];
    3350                            }
    3351                            for (int item = firstItem; item <= lastItem; item++) {
    3352                                renderer.drawItem(g2, state, dataArea, info,
    3353                                        this, xAxis, yAxis, dataset, series, item,
    3354                                        crosshairState, pass);
    3355                            }
    3356                        }
    3357                    }
    3358                }
    3359                else {
    3360                    //render series in forward order
    3361                    for (int pass = 0; pass < passCount; pass++) {
    3362                        int seriesCount = dataset.getSeriesCount();
    3363                        for (int series = 0; series < seriesCount; series++) {
    3364                            int firstItem = 0;
    3365                            int lastItem = dataset.getItemCount(series) - 1;
    3366                            if (state.getProcessVisibleItemsOnly()) {
    3367                                int[] itemBounds = RendererUtilities.findLiveItems(
    3368                                        dataset, series, xAxis.getLowerBound(),
    3369                                        xAxis.getUpperBound());
    3370                                firstItem = itemBounds[0];
    3371                                lastItem = itemBounds[1];
    3372                            }
    3373                            for (int item = firstItem; item <= lastItem; item++) {
    3374                                renderer.drawItem(g2, state, dataArea, info,
    3375                                        this, xAxis, yAxis, dataset, series, item,
    3376                                        crosshairState, pass);
    3377                            }
    3378                        }
    3379                    }
    3380                }
    3381            }
    3382            return foundData;
    3383        }
    3384
    3385        /**
    3386         * Returns the domain axis for a dataset.
    3387         *
    3388         * @param index  the dataset index.
    3389         *
    3390         * @return The axis.
    3391         */
    3392        public ValueAxis getDomainAxisForDataset(int index) {
    3393
    3394            if (index < 0 || index >= getDatasetCount()) {
    3395                throw new IllegalArgumentException("Index " + index
    3396                        + " out of bounds.");
    3397            }
    3398
    3399            ValueAxis valueAxis = null;
    3400            Integer axisIndex = (Integer) this.datasetToDomainAxisMap.get(
    3401                    new Integer(index));
    3402            if (axisIndex != null) {
    3403                valueAxis = getDomainAxis(axisIndex.intValue());
    3404            }
    3405            else {
    3406                valueAxis = getDomainAxis(0);
    3407            }
    3408            return valueAxis;
    3409
    3410        }
    3411
    3412        /**
    3413         * Returns the range axis for a dataset.
    3414         *
    3415         * @param index  the dataset index.
    3416         *
    3417         * @return The axis.
    3418         */
    3419        public ValueAxis getRangeAxisForDataset(int index) {
    3420
    3421            if (index < 0 || index >= getDatasetCount()) {
    3422                throw new IllegalArgumentException("Index " + index
    3423                        + " out of bounds.");
    3424            }
    3425
    3426            ValueAxis valueAxis = null;
    3427            Integer axisIndex
    3428                = (Integer) this.datasetToRangeAxisMap.get(new Integer(index));
    3429            if (axisIndex != null) {
    3430                valueAxis = getRangeAxis(axisIndex.intValue());
    3431            }
    3432            else {
    3433                valueAxis = getRangeAxis(0);
    3434            }
    3435            return valueAxis;
    3436
    3437        }
    3438
    3439        /**
    3440         * Draws the gridlines for the plot, if they are visible.
    3441         *
    3442         * @param g2  the graphics device.
    3443         * @param dataArea  the data area.
    3444         * @param ticks  the ticks.
    3445         *
    3446         * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List)
    3447         */
    3448        protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea,
    3449                                           List ticks) {
    3450
    3451            // no renderer, no gridlines...
    3452            if (getRenderer() == null) {
    3453                return;
    3454            }
    3455
    3456            // draw the domain grid lines, if any...
    3457            if (isDomainGridlinesVisible()) {
    3458                Stroke gridStroke = getDomainGridlineStroke();
    3459                Paint gridPaint = getDomainGridlinePaint();
    3460                if ((gridStroke != null) && (gridPaint != null)) {
    3461                    Iterator iterator = ticks.iterator();
    3462                    while (iterator.hasNext()) {
    3463                        ValueTick tick = (ValueTick) iterator.next();
    3464                        getRenderer().drawDomainGridLine(g2, this, getDomainAxis(),
    3465                                dataArea, tick.getValue());
    3466                    }
    3467                }
    3468            }
    3469        }
    3470
    3471        /**
    3472         * Draws the gridlines for the plot's primary range axis, if they are
    3473         * visible.
    3474         *
    3475         * @param g2  the graphics device.
    3476         * @param area  the data area.
    3477         * @param ticks  the ticks.
    3478         *
    3479         * @see #drawDomainGridlines(Graphics2D, Rectangle2D, List)
    3480         */
    3481        protected void drawRangeGridlines(Graphics2D g2, Rectangle2D area,
    3482                                          List ticks) {
    3483
    3484            // no renderer, no gridlines...
    3485            if (getRenderer() == null) {
    3486                return;
    3487            }
    3488
    3489            // draw the range grid lines, if any...
    3490            if (isRangeGridlinesVisible()) {
    3491                Stroke gridStroke = getRangeGridlineStroke();
    3492                Paint gridPaint = getRangeGridlinePaint();
    3493                ValueAxis axis = getRangeAxis();
    3494                if (axis != null) {
    3495                    Iterator iterator = ticks.iterator();
    3496                    while (iterator.hasNext()) {
    3497                        ValueTick tick = (ValueTick) iterator.next();
    3498                        if (tick.getValue() != 0.0
    3499                                || !isRangeZeroBaselineVisible()) {
    3500                            getRenderer().drawRangeLine(g2, this, getRangeAxis(),
    3501                                    area, tick.getValue(), gridPaint, gridStroke);
    3502                        }
    3503                    }
    3504                }
    3505            }
    3506        }
    3507
    3508        /**
    3509         * Draws a base line across the chart at value zero on the domain axis.
    3510         *
    3511         * @param g2  the graphics device.
    3512         * @param area  the data area.
    3513         *
    3514         * @see #setDomainZeroBaselineVisible(boolean)
    3515         *
    3516         * @since 1.0.5
    3517         */
    3518        protected void drawZeroDomainBaseline(Graphics2D g2, Rectangle2D area) {
    3519            if (isDomainZeroBaselineVisible()) {
    3520                XYItemRenderer r = getRenderer();
    3521                // FIXME: the renderer interface doesn't have the drawDomainLine()
    3522                // method, so we have to rely on the renderer being a subclass of
    3523                // AbstractXYItemRenderer (which is lame)
    3524                if (r instanceof AbstractXYItemRenderer) {
    3525                    AbstractXYItemRenderer renderer = (AbstractXYItemRenderer) r;
    3526                    renderer.drawDomainLine(g2, this, getDomainAxis(), area, 0.0,
    3527                            this.domainZeroBaselinePaint,
    3528                            this.domainZeroBaselineStroke);
    3529                }
    3530            }
    3531        }
    3532
    3533        /**
    3534         * Draws a base line across the chart at value zero on the range axis.
    3535         *
    3536         * @param g2  the graphics device.
    3537         * @param area  the data area.
    3538         *
    3539         * @see #setRangeZeroBaselineVisible(boolean)
    3540         */
    3541        protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) {
    3542            if (isRangeZeroBaselineVisible()) {
    3543                getRenderer().drawRangeLine(g2, this, getRangeAxis(), area, 0.0,
    3544                        this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke);
    3545            }
    3546        }
    3547
    3548        /**
    3549         * Draws the annotations for the plot.
    3550         *
    3551         * @param g2  the graphics device.
    3552         * @param dataArea  the data area.
    3553         * @param info  the chart rendering info.
    3554         */
    3555        public void drawAnnotations(Graphics2D g2,
    3556                                    Rectangle2D dataArea,
    3557                                    PlotRenderingInfo info) {
    3558
    3559            Iterator iterator = this.annotations.iterator();
    3560            while (iterator.hasNext()) {
    3561                XYAnnotation annotation = (XYAnnotation) iterator.next();
    3562                ValueAxis xAxis = getDomainAxis();
    3563                ValueAxis yAxis = getRangeAxis();
    3564                annotation.draw(g2, this, dataArea, xAxis, yAxis, 0, info);
    3565            }
    3566
    3567        }
    3568
    3569        /**
    3570         * Draws the domain markers (if any) for an axis and layer.  This method is
    3571         * typically called from within the draw() method.
    3572         *
    3573         * @param g2  the graphics device.
    3574         * @param dataArea  the data area.
    3575         * @param index  the renderer index.
    3576         * @param layer  the layer (foreground or background).
    3577         */
    3578        protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea,
    3579                                         int index, Layer layer) {
    3580
    3581            XYItemRenderer r = getRenderer(index);
    3582            if (r == null) {
    3583                return;
    3584            }
    3585            // check that the renderer has a corresponding dataset (it doesn't
    3586            // matter if the dataset is null)
    3587            if (index >= getDatasetCount()) {
    3588                return;
    3589            }
    3590            Collection markers = getDomainMarkers(index, layer);
    3591            ValueAxis axis = getDomainAxisForDataset(index);
    3592            if (markers != null && axis != null) {
    3593                Iterator iterator = markers.iterator();
    3594                while (iterator.hasNext()) {
    3595                    Marker marker = (Marker) iterator.next();
    3596                    r.drawDomainMarker(g2, this, axis, marker, dataArea);
    3597                }
    3598            }
    3599
    3600        }
    3601
    3602        /**
    3603         * Draws the range markers (if any) for a renderer and layer.  This method
    3604         * is typically called from within the draw() method.
    3605         *
    3606         * @param g2  the graphics device.
    3607         * @param dataArea  the data area.
    3608         * @param index  the renderer index.
    3609         * @param layer  the layer (foreground or background).
    3610         */
    3611        protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea,
    3612                                        int index, Layer layer) {
    3613
    3614            XYItemRenderer r = getRenderer(index);
    3615            if (r == null) {
    3616                return;
    3617            }
    3618            // check that the renderer has a corresponding dataset (it doesn't
    3619            // matter if the dataset is null)
    3620            if (index >= getDatasetCount()) {
    3621                return;
    3622            }
    3623            Collection markers = getRangeMarkers(index, layer);
    3624            ValueAxis axis = getRangeAxisForDataset(index);
    3625            if (markers != null && axis != null) {
    3626                Iterator iterator = markers.iterator();
    3627                while (iterator.hasNext()) {
    3628                    Marker marker = (Marker) iterator.next();
    3629                    r.drawRangeMarker(g2, this, axis, marker, dataArea);
    3630                }
    3631            }
    3632        }
    3633
    3634        /**
    3635         * Returns the list of domain markers (read only) for the specified layer.
    3636         *
    3637         * @param layer  the layer (foreground or background).
    3638         *
    3639         * @return The list of domain markers.
    3640         *
    3641         * @see #getRangeMarkers(Layer)
    3642         */
    3643        public Collection getDomainMarkers(Layer layer) {
    3644            return getDomainMarkers(0, layer);
    3645        }
    3646
    3647        /**
    3648         * Returns the list of range markers (read only) for the specified layer.
    3649         *
    3650         * @param layer  the layer (foreground or background).
    3651         *
    3652         * @return The list of range markers.
    3653         *
    3654         * @see #getDomainMarkers(Layer)
    3655         */
    3656        public Collection getRangeMarkers(Layer layer) {
    3657            return getRangeMarkers(0, layer);
    3658        }
    3659
    3660        /**
    3661         * Returns a collection of domain markers for a particular renderer and
    3662         * layer.
    3663         *
    3664         * @param index  the renderer index.
    3665         * @param layer  the layer.
    3666         *
    3667         * @return A collection of markers (possibly <code>null</code>).
    3668         *
    3669         * @see #getRangeMarkers(int, Layer)
    3670         */
    3671        public Collection getDomainMarkers(int index, Layer layer) {
    3672            Collection result = null;
    3673            Integer key = new Integer(index);
    3674            if (layer == Layer.FOREGROUND) {
    3675                result = (Collection) this.foregroundDomainMarkers.get(key);
    3676            }
    3677            else if (layer == Layer.BACKGROUND) {
    3678                result = (Collection) this.backgroundDomainMarkers.get(key);
    3679            }
    3680            if (result != null) {
    3681                result = Collections.unmodifiableCollection(result);
    3682            }
    3683            return result;
    3684        }
    3685
    3686        /**
    3687         * Returns a collection of range markers for a particular renderer and
    3688         * layer.
    3689         *
    3690         * @param index  the renderer index.
    3691         * @param layer  the layer.
    3692         *
    3693         * @return A collection of markers (possibly <code>null</code>).
    3694         *
    3695         * @see #getDomainMarkers(int, Layer)
    3696         */
    3697        public Collection getRangeMarkers(int index, Layer layer) {
    3698            Collection result = null;
    3699            Integer key = new Integer(index);
    3700            if (layer == Layer.FOREGROUND) {
    3701                result = (Collection) this.foregroundRangeMarkers.get(key);
    3702            }
    3703            else if (layer == Layer.BACKGROUND) {
    3704                result = (Collection) this.backgroundRangeMarkers.get(key);
    3705            }
    3706            if (result != null) {
    3707                result = Collections.unmodifiableCollection(result);
    3708            }
    3709            return result;
    3710        }
    3711
    3712        /**
    3713         * Utility method for drawing a horizontal line across the data area of the
    3714         * plot.
    3715         *
    3716         * @param g2  the graphics device.
    3717         * @param dataArea  the data area.
    3718         * @param value  the coordinate, where to draw the line.
    3719         * @param stroke  the stroke to use.
    3720         * @param paint  the paint to use.
    3721         */
    3722        protected void drawHorizontalLine(Graphics2D g2, Rectangle2D dataArea,
    3723                                          double value, Stroke stroke,
    3724                                          Paint paint) {
    3725
    3726            ValueAxis axis = getRangeAxis();
    3727            if (getOrientation() == PlotOrientation.HORIZONTAL) {
    3728                axis = getDomainAxis();
    3729            }
    3730            if (axis.getRange().contains(value)) {
    3731                double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT);
    3732                Line2D line = new Line2D.Double(dataArea.getMinX(), yy,
    3733                        dataArea.getMaxX(), yy);
    3734                g2.setStroke(stroke);
    3735                g2.setPaint(paint);
    3736                g2.draw(line);
    3737            }
    3738
    3739        }
    3740
    3741        /**
    3742         * Draws a domain crosshair.
    3743         *
    3744         * @param g2  the graphics target.
    3745         * @param dataArea  the data area.
    3746         * @param orientation  the plot orientation.
    3747         * @param value  the crosshair value.
    3748         * @param axis  the axis against which the value is measured.
    3749         * @param stroke  the stroke used to draw the crosshair line.
    3750         * @param paint  the paint used to draw the crosshair line.
    3751         *
    3752         * @since 1.0.4
    3753         */
    3754        protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea,
    3755                PlotOrientation orientation, double value, ValueAxis axis,
    3756                Stroke stroke, Paint paint) {
    3757
    3758            if (axis.getRange().contains(value)) {
    3759                Line2D line = null;
    3760                if (orientation == PlotOrientation.VERTICAL) {
    3761                    double xx = axis.valueToJava2D(value, dataArea,
    3762                            RectangleEdge.BOTTOM);
    3763                    line = new Line2D.Double(xx, dataArea.getMinY(), xx,
    3764                            dataArea.getMaxY());
    3765                }
    3766                else {
    3767                    double yy = axis.valueToJava2D(value, dataArea,
    3768                            RectangleEdge.LEFT);
    3769                    line = new Line2D.Double(dataArea.getMinX(), yy,
    3770                            dataArea.getMaxX(), yy);
    3771                }
    3772                g2.setStroke(stroke);
    3773                g2.setPaint(paint);
    3774                g2.draw(line);
    3775            }
    3776
    3777        }
    3778
    3779        /**
    3780         * Utility method for drawing a vertical line on the data area of the plot.
    3781         *
    3782         * @param g2  the graphics device.
    3783         * @param dataArea  the data area.
    3784         * @param value  the coordinate, where to draw the line.
    3785         * @param stroke  the stroke to use.
    3786         * @param paint  the paint to use.
    3787         */
    3788        protected void drawVerticalLine(Graphics2D g2, Rectangle2D dataArea,
    3789                                        double value, Stroke stroke, Paint paint) {
    3790
    3791            ValueAxis axis = getDomainAxis();
    3792            if (getOrientation() == PlotOrientation.HORIZONTAL) {
    3793                axis = getRangeAxis();
    3794            }
    3795            if (axis.getRange().contains(value)) {
    3796                double xx = axis.valueToJava2D(value, dataArea,
    3797                        RectangleEdge.BOTTOM);
    3798                Line2D line = new Line2D.Double(xx, dataArea.getMinY(), xx,
    3799                        dataArea.getMaxY());
    3800                g2.setStroke(stroke);
    3801                g2.setPaint(paint);
    3802                g2.draw(line);
    3803            }
    3804
    3805        }
    3806
    3807        /**
    3808         * Draws a range crosshair.
    3809         *
    3810         * @param g2  the graphics target.
    3811         * @param dataArea  the data area.
    3812         * @param orientation  the plot orientation.
    3813         * @param value  the crosshair value.
    3814         * @param axis  the axis against which the value is measured.
    3815         * @param stroke  the stroke used to draw the crosshair line.
    3816         * @param paint  the paint used to draw the crosshair line.
    3817         *
    3818         * @since 1.0.4
    3819         */
    3820        protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea,
    3821                PlotOrientation orientation, double value, ValueAxis axis,
    3822                Stroke stroke, Paint paint) {
    3823
    3824            if (axis.getRange().contains(value)) {
    3825                Line2D line = null;
    3826                if (orientation == PlotOrientation.HORIZONTAL) {
    3827                    double xx = axis.valueToJava2D(value, dataArea,
    3828                            RectangleEdge.BOTTOM);
    3829                    line = new Line2D.Double(xx, dataArea.getMinY(), xx,
    3830                            dataArea.getMaxY());
    3831                }
    3832                else {
    3833                    double yy = axis.valueToJava2D(value, dataArea,
    3834                            RectangleEdge.LEFT);
    3835                    line = new Line2D.Double(dataArea.getMinX(), yy,
    3836                            dataArea.getMaxX(), yy);
    3837                }
    3838                g2.setStroke(stroke);
    3839                g2.setPaint(paint);
    3840                g2.draw(line);
    3841            }
    3842
    3843        }
    3844
    3845        /**
    3846         * Handles a 'click' on the plot by updating the anchor values.
    3847         *
    3848         * @param x  the x-coordinate, where the click occurred, in Java2D space.
    3849         * @param y  the y-coordinate, where the click occurred, in Java2D space.
    3850         * @param info  object containing information about the plot dimensions.
    3851         */
    3852        public void handleClick(int x, int y, PlotRenderingInfo info) {
    3853
    3854            Rectangle2D dataArea = info.getDataArea();
    3855            if (dataArea.contains(x, y)) {
    3856                // set the anchor value for the horizontal axis...
    3857                ValueAxis xaxis = getDomainAxis();
    3858                if (xaxis != null) {
    3859                    double hvalue = xaxis.java2DToValue(x, info.getDataArea(),
    3860                            getDomainAxisEdge());
    3861                    setDomainCrosshairValue(hvalue);
    3862                }
    3863
    3864                // set the anchor value for the vertical axis...
    3865                ValueAxis yaxis = getRangeAxis();
    3866                if (yaxis != null) {
    3867                    double vvalue = yaxis.java2DToValue(y, info.getDataArea(),
    3868                            getRangeAxisEdge());
    3869                    setRangeCrosshairValue(vvalue);
    3870                }
    3871            }
    3872        }
    3873
    3874        /**
    3875         * A utility method that returns a list of datasets that are mapped to a
    3876         * particular axis.
    3877         *
    3878         * @param axisIndex  the axis index (<code>null</code> not permitted).
    3879         *
    3880         * @return A list of datasets.
    3881         */
    3882        private List getDatasetsMappedToDomainAxis(Integer axisIndex) {
    3883            if (axisIndex == null) {
    3884                throw new IllegalArgumentException("Null 'axisIndex' argument.");
    3885            }
    3886            List result = new ArrayList();
    3887            for (int i = 0; i < this.datasets.size(); i++) {
    3888                Integer mappedAxis = (Integer) this.datasetToDomainAxisMap.get(
    3889                        new Integer(i));
    3890                if (mappedAxis == null) {
    3891                    if (axisIndex.equals(ZERO)) {
    3892                        result.add(this.datasets.get(i));
    3893                    }
    3894                }
    3895                else {
    3896                    if (mappedAxis.equals(axisIndex)) {
    3897                        result.add(this.datasets.get(i));
    3898                    }
    3899                }
    3900            }
    3901            return result;
    3902        }
    3903
    3904        /**
    3905         * A utility method that returns a list of datasets that are mapped to a
    3906         * particular axis.
    3907         *
    3908         * @param axisIndex  the axis index (<code>null</code> not permitted).
    3909         *
    3910         * @return A list of datasets.
    3911         */
    3912        private List getDatasetsMappedToRangeAxis(Integer axisIndex) {
    3913            if (axisIndex == null) {
    3914                throw new IllegalArgumentException("Null 'axisIndex' argument.");
    3915            }
    3916            List result = new ArrayList();
    3917            for (int i = 0; i < this.datasets.size(); i++) {
    3918                Integer mappedAxis = (Integer) this.datasetToRangeAxisMap.get(
    3919                        new Integer(i));
    3920                if (mappedAxis == null) {
    3921                    if (axisIndex.equals(ZERO)) {
    3922                        result.add(this.datasets.get(i));
    3923                    }
    3924                }
    3925                else {
    3926                    if (mappedAxis.equals(axisIndex)) {
    3927                        result.add(this.datasets.get(i));
    3928                    }
    3929                }
    3930            }
    3931            return result;
    3932        }
    3933
    3934        /**
    3935         * Returns the index of the given domain axis.
    3936         *
    3937         * @param axis  the axis.
    3938         *
    3939         * @return The axis index.
    3940         *
    3941         * @see #getRangeAxisIndex(ValueAxis)
    3942         */
    3943        public int getDomainAxisIndex(ValueAxis axis) {
    3944            int result = this.domainAxes.indexOf(axis);
    3945            if (result < 0) {
    3946                // try the parent plot
    3947                Plot parent = getParent();
    3948                if (parent instanceof XYPlot) {
    3949                    XYPlot p = (XYPlot) parent;
    3950                    result = p.getDomainAxisIndex(axis);
    3951                }
    3952            }
    3953            return result;
    3954        }
    3955
    3956        /**
    3957         * Returns the index of the given range axis.
    3958         *
    3959         * @param axis  the axis.
    3960         *
    3961         * @return The axis index.
    3962         *
    3963         * @see #getDomainAxisIndex(ValueAxis)
    3964         */
    3965        public int getRangeAxisIndex(ValueAxis axis) {
    3966            int result = this.rangeAxes.indexOf(axis);
    3967            if (result < 0) {
    3968                // try the parent plot
    3969                Plot parent = getParent();
    3970                if (parent instanceof XYPlot) {
    3971                    XYPlot p = (XYPlot) parent;
    3972                    result = p.getRangeAxisIndex(axis);
    3973                }
    3974            }
    3975            return result;
    3976        }
    3977
    3978        /**
    3979         * Returns the range for the specified axis.
    3980         *
    3981         * @param axis  the axis.
    3982         *
    3983         * @return The range.
    3984         */
    3985        public Range getDataRange(ValueAxis axis) {
    3986
    3987            Range result = null;
    3988            List mappedDatasets = new ArrayList();
    3989            boolean isDomainAxis = true;
    3990
    3991            // is it a domain axis?
    3992            int domainIndex = getDomainAxisIndex(axis);
    3993            if (domainIndex >= 0) {
    3994                isDomainAxis = true;
    3995                mappedDatasets.addAll(getDatasetsMappedToDomainAxis(
    3996                        new Integer(domainIndex)));
    3997            }
    3998
    3999            // or is it a range axis?
    4000            int rangeIndex = getRangeAxisIndex(axis);
    4001            if (rangeIndex >= 0) {
    4002                isDomainAxis = false;
    4003                mappedDatasets.addAll(getDatasetsMappedToRangeAxis(
    4004                        new Integer(rangeIndex)));
    4005            }
    4006
    4007            // iterate through the datasets that map to the axis and get the union
    4008            // of the ranges.
    4009            Iterator iterator = mappedDatasets.iterator();
    4010            while (iterator.hasNext()) {
    4011                XYDataset d = (XYDataset) iterator.next();
    4012                if (d != null) {
    4013                    XYItemRenderer r = getRendererForDataset(d);
    4014                    if (isDomainAxis) {
    4015                        if (r != null) {
    4016                            result = Range.combine(result, r.findDomainBounds(d));
    4017                        }
    4018                        else {
    4019                            result = Range.combine(result,
    4020                                    DatasetUtilities.findDomainBounds(d));
    4021                        }
    4022                    }
    4023                    else {
    4024                        if (r != null) {
    4025                            result = Range.combine(result, r.findRangeBounds(d));
    4026                        }
    4027                        else {
    4028                            result = Range.combine(result,
    4029                                    DatasetUtilities.findRangeBounds(d));
    4030                        }
    4031                    }
    4032                }
    4033            }
    4034            return result;
    4035
    4036        }
    4037
    4038        /**
    4039         * Receives notification of a change to the plot's dataset.
    4040         * <P>
    4041         * The axis ranges are updated if necessary.
    4042         *
    4043         * @param event  information about the event (not used here).
    4044         */
    4045        public void datasetChanged(DatasetChangeEvent event) {
    4046            configureDomainAxes();
    4047            configureRangeAxes();
    4048            if (getParent() != null) {
    4049                getParent().datasetChanged(event);
    4050            }
    4051            else {
    4052                PlotChangeEvent e = new PlotChangeEvent(this);
    4053                e.setType(ChartChangeEventType.DATASET_UPDATED);
    4054                notifyListeners(e);
    4055            }
    4056        }
    4057
    4058        /**
    4059         * Receives notification of a renderer change event.
    4060         *
    4061         * @param event  the event.
    4062         */
    4063        public void rendererChanged(RendererChangeEvent event) {
    4064            fireChangeEvent();
    4065        }
    4066
    4067        /**
    4068         * Returns a flag indicating whether or not the domain crosshair is visible.
    4069         *
    4070         * @return The flag.
    4071         *
    4072         * @see #setDomainCrosshairVisible(boolean)
    4073         */
    4074        public boolean isDomainCrosshairVisible() {
    4075            return this.domainCrosshairVisible;
    4076        }
    4077
    4078        /**
    4079         * Sets the flag indicating whether or not the domain crosshair is visible
    4080         * and, if the flag changes, sends a {@link PlotChangeEvent} to all
    4081         * registered listeners.
    4082         *
    4083         * @param flag  the new value of the flag.
    4084         *
    4085         * @see #isDomainCrosshairVisible()
    4086         */
    4087        public void setDomainCrosshairVisible(boolean flag) {
    4088            if (this.domainCrosshairVisible != flag) {
    4089                this.domainCrosshairVisible = flag;
    4090                fireChangeEvent();
    4091            }
    4092        }
    4093
    4094        /**
    4095         * Returns a flag indicating whether or not the crosshair should "lock-on"
    4096         * to actual data values.
    4097         *
    4098         * @return The flag.
    4099         *
    4100         * @see #setDomainCrosshairLockedOnData(boolean)
    4101         */
    4102        public boolean isDomainCrosshairLockedOnData() {
    4103            return this.domainCrosshairLockedOnData;
    4104        }
    4105
    4106        /**
    4107         * Sets the flag indicating whether or not the domain crosshair should
    4108         * "lock-on" to actual data values.  If the flag value changes, this
    4109         * method sends a {@link PlotChangeEvent} to all registered listeners.
    4110         *
    4111         * @param flag  the flag.
    4112         *
    4113         * @see #isDomainCrosshairLockedOnData()
    4114         */
    4115        public void setDomainCrosshairLockedOnData(boolean flag) {
    4116            if (this.domainCrosshairLockedOnData != flag) {
    4117                this.domainCrosshairLockedOnData = flag;
    4118                fireChangeEvent();
    4119            }
    4120        }
    4121
    4122        /**
    4123         * Returns the domain crosshair value.
    4124         *
    4125         * @return The value.
    4126         *
    4127         * @see #setDomainCrosshairValue(double)
    4128         */
    4129        public double getDomainCrosshairValue() {
    4130            return this.domainCrosshairValue;
    4131        }
    4132
    4133        /**
    4134         * Sets the domain crosshair value and sends a {@link PlotChangeEvent} to
    4135         * all registered listeners (provided that the domain crosshair is visible).
    4136         *
    4137         * @param value  the value.
    4138         *
    4139         * @see #getDomainCrosshairValue()
    4140         */
    4141        public void setDomainCrosshairValue(double value) {
    4142            setDomainCrosshairValue(value, true);
    4143        }
    4144
    4145        /**
    4146         * Sets the domain crosshair value and, if requested, sends a
    4147         * {@link PlotChangeEvent} to all registered listeners (provided that the
    4148         * domain crosshair is visible).
    4149         *
    4150         * @param value  the new value.
    4151         * @param notify  notify listeners?
    4152         *
    4153         * @see #getDomainCrosshairValue()
    4154         */
    4155        public void setDomainCrosshairValue(double value, boolean notify) {
    4156            this.domainCrosshairValue = value;
    4157            if (isDomainCrosshairVisible() && notify) {
    4158                fireChangeEvent();
    4159            }
    4160        }
    4161
    4162        /**
    4163         * Returns the {@link Stroke} used to draw the crosshair (if visible).
    4164         *
    4165         * @return The crosshair stroke (never <code>null</code>).
    4166         *
    4167         * @see #setDomainCrosshairStroke(Stroke)
    4168         * @see #isDomainCrosshairVisible()
    4169         * @see #getDomainCrosshairPaint()
    4170         */
    4171        public Stroke getDomainCrosshairStroke() {
    4172            return this.domainCrosshairStroke;
    4173        }
    4174
    4175        /**
    4176         * Sets the Stroke used to draw the crosshairs (if visible) and notifies
    4177         * registered listeners that the axis has been modified.
    4178         *
    4179         * @param stroke  the new crosshair stroke (<code>null</code> not
    4180         *     permitted).
    4181         *
    4182         * @see #getDomainCrosshairStroke()
    4183         */
    4184        public void setDomainCrosshairStroke(Stroke stroke) {
    4185            if (stroke == null) {
    4186                throw new IllegalArgumentException("Null 'stroke' argument.");
    4187            }
    4188            this.domainCrosshairStroke = stroke;
    4189            fireChangeEvent();
    4190        }
    4191
    4192        /**
    4193         * Returns the domain crosshair paint.
    4194         *
    4195         * @return The crosshair paint (never <code>null</code>).
    4196         *
    4197         * @see #setDomainCrosshairPaint(Paint)
    4198         * @see #isDomainCrosshairVisible()
    4199         * @see #getDomainCrosshairStroke()
    4200         */
    4201        public Paint getDomainCrosshairPaint() {
    4202            return this.domainCrosshairPaint;
    4203        }
    4204
    4205        /**
    4206         * Sets the paint used to draw the crosshairs (if visible) and sends a
    4207         * {@link PlotChangeEvent} to all registered listeners.
    4208         *
    4209         * @param paint the new crosshair paint (<code>null</code> not permitted).
    4210         *
    4211         * @see #getDomainCrosshairPaint()
    4212         */
    4213        public void setDomainCrosshairPaint(Paint paint) {
    4214            if (paint == null) {
    4215                throw new IllegalArgumentException("Null 'paint' argument.");
    4216            }
    4217            this.domainCrosshairPaint = paint;
    4218            fireChangeEvent();
    4219        }
    4220
    4221        /**
    4222         * Returns a flag indicating whether or not the range crosshair is visible.
    4223         *
    4224         * @return The flag.
    4225         *
    4226         * @see #setRangeCrosshairVisible(boolean)
    4227         * @see #isDomainCrosshairVisible()
    4228         */
    4229        public boolean isRangeCrosshairVisible() {
    4230            return this.rangeCrosshairVisible;
    4231        }
    4232
    4233        /**
    4234         * Sets the flag indicating whether or not the range crosshair is visible.
    4235         * If the flag value changes, this method sends a {@link PlotChangeEvent}
    4236         * to all registered listeners.
    4237         *
    4238         * @param flag  the new value of the flag.
    4239         *
    4240         * @see #isRangeCrosshairVisible()
    4241         */
    4242        public void setRangeCrosshairVisible(boolean flag) {
    4243            if (this.rangeCrosshairVisible != flag) {
    4244                this.rangeCrosshairVisible = flag;
    4245                fireChangeEvent();
    4246            }
    4247        }
    4248
    4249        /**
    4250         * Returns a flag indicating whether or not the crosshair should "lock-on"
    4251         * to actual data values.
    4252         *
    4253         * @return The flag.
    4254         *
    4255         * @see #setRangeCrosshairLockedOnData(boolean)
    4256         */
    4257        public boolean isRangeCrosshairLockedOnData() {
    4258            return this.rangeCrosshairLockedOnData;
    4259        }
    4260
    4261        /**
    4262         * Sets the flag indicating whether or not the range crosshair should
    4263         * "lock-on" to actual data values.  If the flag value changes, this method
    4264         * sends a {@link PlotChangeEvent} to all registered listeners.
    4265         *
    4266         * @param flag  the flag.
    4267         *
    4268         * @see #isRangeCrosshairLockedOnData()
    4269         */
    4270        public void setRangeCrosshairLockedOnData(boolean flag) {
    4271            if (this.rangeCrosshairLockedOnData != flag) {
    4272                this.rangeCrosshairLockedOnData = flag;
    4273                fireChangeEvent();
    4274            }
    4275        }
    4276
    4277        /**
    4278         * Returns the range crosshair value.
    4279         *
    4280         * @return The value.
    4281         *
    4282         * @see #setRangeCrosshairValue(double)
    4283         */
    4284        public double getRangeCrosshairValue() {
    4285            return this.rangeCrosshairValue;
    4286        }
    4287
    4288        /**
    4289         * Sets the range crosshair value.
    4290         * <P>
    4291         * Registered listeners are notified that the plot has been modified, but
    4292         * only if the crosshair is visible.
    4293         *
    4294         * @param value  the new value.
    4295         *
    4296         * @see #getRangeCrosshairValue()
    4297         */
    4298        public void setRangeCrosshairValue(double value) {
    4299            setRangeCrosshairValue(value, true);
    4300        }
    4301
    4302        /**
    4303         * Sets the range crosshair value and sends a {@link PlotChangeEvent} to
    4304         * all registered listeners, but only if the crosshair is visible.
    4305         *
    4306         * @param value  the new value.
    4307         * @param notify  a flag that controls whether or not listeners are
    4308         *                notified.
    4309         *
    4310         * @see #getRangeCrosshairValue()
    4311         */
    4312        public void setRangeCrosshairValue(double value, boolean notify) {
    4313            this.rangeCrosshairValue = value;
    4314            if (isRangeCrosshairVisible() && notify) {
    4315                fireChangeEvent();
    4316            }
    4317        }
    4318
    4319        /**
    4320         * Returns the stroke used to draw the crosshair (if visible).
    4321         *
    4322         * @return The crosshair stroke (never <code>null</code>).
    4323         *
    4324         * @see #setRangeCrosshairStroke(Stroke)
    4325         * @see #isRangeCrosshairVisible()
    4326         * @see #getRangeCrosshairPaint()
    4327         */
    4328        public Stroke getRangeCrosshairStroke() {
    4329            return this.rangeCrosshairStroke;
    4330        }
    4331
    4332        /**
    4333         * Sets the stroke used to draw the crosshairs (if visible) and sends a
    4334         * {@link PlotChangeEvent} to all registered listeners.
    4335         *
    4336         * @param stroke  the new crosshair stroke (<code>null</code> not
    4337         *         permitted).
    4338         *
    4339         * @see #getRangeCrosshairStroke()
    4340         */
    4341        public void setRangeCrosshairStroke(Stroke stroke) {
    4342            if (stroke == null) {
    4343                throw new IllegalArgumentException("Null 'stroke' argument.");
    4344            }
    4345            this.rangeCrosshairStroke = stroke;
    4346            fireChangeEvent();
    4347        }
    4348
    4349        /**
    4350         * Returns the range crosshair paint.
    4351         *
    4352         * @return The crosshair paint (never <code>null</code>).
    4353         *
    4354         * @see #setRangeCrosshairPaint(Paint)
    4355         * @see #isRangeCrosshairVisible()
    4356         * @see #getRangeCrosshairStroke()
    4357         */
    4358        public Paint getRangeCrosshairPaint() {
    4359            return this.rangeCrosshairPaint;
    4360        }
    4361
    4362        /**
    4363         * Sets the paint used to color the crosshairs (if visible) and sends a
    4364         * {@link PlotChangeEvent} to all registered listeners.
    4365         *
    4366         * @param paint the new crosshair paint (<code>null</code> not permitted).
    4367         *
    4368         * @see #getRangeCrosshairPaint()
    4369         */
    4370        public void setRangeCrosshairPaint(Paint paint) {
    4371            if (paint == null) {
    4372                throw new IllegalArgumentException("Null 'paint' argument.");
    4373            }
    4374            this.rangeCrosshairPaint = paint;
    4375            fireChangeEvent();
    4376        }
    4377
    4378        /**
    4379         * Returns the fixed domain axis space.
    4380         *
    4381         * @return The fixed domain axis space (possibly <code>null</code>).
    4382         *
    4383         * @see #setFixedDomainAxisSpace(AxisSpace)
    4384         */
    4385        public AxisSpace getFixedDomainAxisSpace() {
    4386            return this.fixedDomainAxisSpace;
    4387        }
    4388
    4389        /**
    4390         * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
    4391         * all registered listeners.
    4392         *
    4393         * @param space  the space (<code>null</code> permitted).
    4394         *
    4395         * @see #getFixedDomainAxisSpace()
    4396         */
    4397        public void setFixedDomainAxisSpace(AxisSpace space) {
    4398            setFixedDomainAxisSpace(space, true);
    4399        }
    4400
    4401        /**
    4402         * Sets the fixed domain axis space and, if requested, sends a
    4403         * {@link PlotChangeEvent} to all registered listeners.
    4404         *
    4405         * @param space  the space (<code>null</code> permitted).
    4406         * @param notify  notify listeners?
    4407         *
    4408         * @see #getFixedDomainAxisSpace()
    4409         *
    4410         * @since 1.0.9
    4411         */
    4412        public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) {
    4413            this.fixedDomainAxisSpace = space;
    4414            if (notify) {
    4415                fireChangeEvent();
    4416            }
    4417        }
    4418
    4419        /**
    4420         * Returns the fixed range axis space.
    4421         *
    4422         * @return The fixed range axis space (possibly <code>null</code>).
    4423         *
    4424         * @see #setFixedRangeAxisSpace(AxisSpace)
    4425         */
    4426        public AxisSpace getFixedRangeAxisSpace() {
    4427            return this.fixedRangeAxisSpace;
    4428        }
    4429
    4430        /**
    4431         * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
    4432         * all registered listeners.
    4433         *
    4434         * @param space  the space (<code>null</code> permitted).
    4435         *
    4436         * @see #getFixedRangeAxisSpace()
    4437         */
    4438        public void setFixedRangeAxisSpace(AxisSpace space) {
    4439            setFixedRangeAxisSpace(space, true);
    4440        }
    4441
    4442        /**
    4443         * Sets the fixed range axis space and, if requested, sends a
    4444         * {@link PlotChangeEvent} to all registered listeners.
    4445         *
    4446         * @param space  the space (<code>null</code> permitted).
    4447         * @param notify  notify listeners?
    4448         *
    4449         * @see #getFixedRangeAxisSpace()
    4450         *
    4451         * @since 1.0.9
    4452         */
    4453        public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) {
    4454            this.fixedRangeAxisSpace = space;
    4455            if (notify) {
    4456                fireChangeEvent();
    4457            }
    4458        }
    4459
    4460        /**
    4461         * Multiplies the range on the domain axis/axes by the specified factor.
    4462         *
    4463         * @param factor  the zoom factor.
    4464         * @param info  the plot rendering info.
    4465         * @param source  the source point (in Java2D space).
    4466         *
    4467         * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D)
    4468         */
    4469        public void zoomDomainAxes(double factor, PlotRenderingInfo info,
    4470                                   Point2D source) {
    4471            // delegate to other method
    4472            zoomDomainAxes(factor, info, source, false);
    4473        }
    4474
    4475        /**
    4476         * Multiplies the range on the domain axis/axes by the specified factor.
    4477         *
    4478         * @param factor  the zoom factor.
    4479         * @param info  the plot rendering info.
    4480         * @param source  the source point (in Java2D space).
    4481         * @param useAnchor  use source point as zoom anchor?
    4482         *
    4483         * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean)
    4484         *
    4485         * @since 1.0.7
    4486         */
    4487        public void zoomDomainAxes(double factor, PlotRenderingInfo info,
    4488                                   Point2D source, boolean useAnchor) {
    4489
    4490            // perform the zoom on each domain axis
    4491            for (int i = 0; i < this.domainAxes.size(); i++) {
    4492                ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
    4493                if (domainAxis != null) {
    4494                    if (useAnchor) {
    4495                        // get the relevant source coordinate given the plot
    4496                        // orientation
    4497                        double sourceX = source.getX();
    4498                        if (this.orientation == PlotOrientation.HORIZONTAL) {
    4499                            sourceX = source.getY();
    4500                        }
    4501                        double anchorX = domainAxis.java2DToValue(sourceX,
    4502                                info.getDataArea(), getDomainAxisEdge());
    4503                        domainAxis.resizeRange(factor, anchorX);
    4504                    }
    4505                    else {
    4506                        domainAxis.resizeRange(factor);
    4507                    }
    4508                }
    4509            }
    4510        }
    4511
    4512        /**
    4513         * Zooms in on the domain axis/axes.  The new lower and upper bounds are
    4514         * specified as percentages of the current axis range, where 0 percent is
    4515         * the current lower bound and 100 percent is the current upper bound.
    4516         *
    4517         * @param lowerPercent  a percentage that determines the new lower bound
    4518         *                      for the axis (e.g. 0.20 is twenty percent).
    4519         * @param upperPercent  a percentage that determines the new upper bound
    4520         *                      for the axis (e.g. 0.80 is eighty percent).
    4521         * @param info  the plot rendering info.
    4522         * @param source  the source point (ignored).
    4523         *
    4524         * @see #zoomRangeAxes(double, double, PlotRenderingInfo, Point2D)
    4525         */
    4526        public void zoomDomainAxes(double lowerPercent, double upperPercent,
    4527                                   PlotRenderingInfo info, Point2D source) {
    4528            for (int i = 0; i < this.domainAxes.size(); i++) {
    4529                ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
    4530                if (domainAxis != null) {
    4531                    domainAxis.zoomRange(lowerPercent, upperPercent);
    4532                }
    4533            }
    4534        }
    4535
    4536        /**
    4537         * Multiplies the range on the range axis/axes by the specified factor.
    4538         *
    4539         * @param factor  the zoom factor.
    4540         * @param info  the plot rendering info.
    4541         * @param source  the source point.
    4542         *
    4543         * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
    4544         */
    4545        public void zoomRangeAxes(double factor, PlotRenderingInfo info,
    4546                                  Point2D source) {
    4547            // delegate to other method
    4548            zoomRangeAxes(factor, info, source, false);
    4549        }
    4550
    4551        /**
    4552         * Multiplies the range on the range axis/axes by the specified factor.
    4553         *
    4554         * @param factor  the zoom factor.
    4555         * @param info  the plot rendering info.
    4556         * @param source  the source point.
    4557         * @param useAnchor  a flag that controls whether or not the source point
    4558         *         is used for the zoom anchor.
    4559         *
    4560         * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
    4561         *
    4562         * @since 1.0.7
    4563         */
    4564        public void zoomRangeAxes(double factor, PlotRenderingInfo info,
    4565                                  Point2D source, boolean useAnchor) {
    4566
    4567            // perform the zoom on each range axis
    4568            for (int i = 0; i < this.rangeAxes.size(); i++) {
    4569                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
    4570                if (rangeAxis != null) {
    4571                    if (useAnchor) {
    4572                        // get the relevant source coordinate given the plot
    4573                        // orientation
    4574                        double sourceY = source.getY();
    4575                        if (this.orientation == PlotOrientation.HORIZONTAL) {
    4576                            sourceY = source.getX();
    4577                        }
    4578                        double anchorY = rangeAxis.java2DToValue(sourceY,
    4579                                info.getDataArea(), getRangeAxisEdge());
    4580                        rangeAxis.resizeRange(factor, anchorY);
    4581                    }
    4582                    else {
    4583                        rangeAxis.resizeRange(factor);
    4584                    }
    4585                }
    4586            }
    4587        }
    4588
    4589        /**
    4590         * Zooms in on the range axes.
    4591         *
    4592         * @param lowerPercent  the lower bound.
    4593         * @param upperPercent  the upper bound.
    4594         * @param info  the plot rendering info.
    4595         * @param source  the source point.
    4596         *
    4597         * @see #zoomDomainAxes(double, double, PlotRenderingInfo, Point2D)
    4598         */
    4599        public void zoomRangeAxes(double lowerPercent, double upperPercent,
    4600                                  PlotRenderingInfo info, Point2D source) {
    4601            for (int i = 0; i < this.rangeAxes.size(); i++) {
    4602                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
    4603                if (rangeAxis != null) {
    4604                    rangeAxis.zoomRange(lowerPercent, upperPercent);
    4605                }
    4606            }
    4607        }
    4608
    4609        /**
    4610         * Returns <code>true</code>, indicating that the domain axis/axes for this
    4611         * plot are zoomable.
    4612         *
    4613         * @return A boolean.
    4614         *
    4615         * @see #isRangeZoomable()
    4616         */
    4617        public boolean isDomainZoomable() {
    4618            return true;
    4619        }
    4620
    4621        /**
    4622         * Returns <code>true</code>, indicating that the range axis/axes for this
    4623         * plot are zoomable.
    4624         *
    4625         * @return A boolean.
    4626         *
    4627         * @see #isDomainZoomable()
    4628         */
    4629        public boolean isRangeZoomable() {
    4630            return true;
    4631        }
    4632
    4633        /**
    4634         * Returns the number of series in the primary dataset for this plot.  If
    4635         * the dataset is <code>null</code>, the method returns 0.
    4636         *
    4637         * @return The series count.
    4638         */
    4639        public int getSeriesCount() {
    4640            int result = 0;
    4641            XYDataset dataset = getDataset();
    4642            if (dataset != null) {
    4643                result = dataset.getSeriesCount();
    4644            }
    4645            return result;
    4646        }
    4647
    4648        /**
    4649         * Returns the fixed legend items, if any.
    4650         *
    4651         * @return The legend items (possibly <code>null</code>).
    4652         *
    4653         * @see #setFixedLegendItems(LegendItemCollection)
    4654         */
    4655        public LegendItemCollection getFixedLegendItems() {
    4656            return this.fixedLegendItems;
    4657        }
    4658
    4659        /**
    4660         * Sets the fixed legend items for the plot.  Leave this set to
    4661         * <code>null</code> if you prefer the legend items to be created
    4662         * automatically.
    4663         *
    4664         * @param items  the legend items (<code>null</code> permitted).
    4665         *
    4666         * @see #getFixedLegendItems()
    4667         */
    4668        public void setFixedLegendItems(LegendItemCollection items) {
    4669            this.fixedLegendItems = items;
    4670            fireChangeEvent();
    4671        }
    4672
    4673        /**
    4674         * Returns the legend items for the plot.  Each legend item is generated by
    4675         * the plot's renderer, since the renderer is responsible for the visual
    4676         * representation of the data.
    4677         *
    4678         * @return The legend items.
    4679         */
    4680        public LegendItemCollection getLegendItems() {
    4681            if (this.fixedLegendItems != null) {
    4682                return this.fixedLegendItems;
    4683            }
    4684            LegendItemCollection result = new LegendItemCollection();
    4685            int count = this.datasets.size();
    4686            for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
    4687                XYDataset dataset = getDataset(datasetIndex);
    4688                if (dataset != null) {
    4689                    XYItemRenderer renderer = getRenderer(datasetIndex);
    4690                    if (renderer == null) {
    4691                        renderer = getRenderer(0);
    4692                    }
    4693                    if (renderer != null) {
    4694                        int seriesCount = dataset.getSeriesCount();
    4695                        for (int i = 0; i < seriesCount; i++) {
    4696                            if (renderer.isSeriesVisible(i)
    4697                                    && renderer.isSeriesVisibleInLegend(i)) {
    4698                                LegendItem item = renderer.getLegendItem(
    4699                                        datasetIndex, i);
    4700                                if (item != null) {
    4701                                    result.add(item);
    4702                                }
    4703                            }
    4704                        }
    4705                    }
    4706                }
    4707            }
    4708            return result;
    4709        }
    4710
    4711        /**
    4712         * Tests this plot for equality with another object.
    4713         *
    4714         * @param obj  the object (<code>null</code> permitted).
    4715         *
    4716         * @return <code>true</code> or <code>false</code>.
    4717         */
    4718        public boolean equals(Object obj) {
    4719
    4720            if (obj == this) {
    4721                return true;
    4722            }
    4723            if (!(obj instanceof XYPlot)) {
    4724                return false;
    4725            }
    4726
    4727            XYPlot that = (XYPlot) obj;
    4728            if (this.weight != that.weight) {
    4729                return false;
    4730            }
    4731            if (this.orientation != that.orientation) {
    4732                return false;
    4733            }
    4734            if (!this.domainAxes.equals(that.domainAxes)) {
    4735                return false;
    4736            }
    4737            if (!this.domainAxisLocations.equals(that.domainAxisLocations)) {
    4738                return false;
    4739            }
    4740            if (this.rangeCrosshairLockedOnData
    4741                    != that.rangeCrosshairLockedOnData) {
    4742                return false;
    4743            }
    4744            if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
    4745                return false;
    4746            }
    4747            if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) {
    4748                return false;
    4749            }
    4750            if (this.domainZeroBaselineVisible != that.domainZeroBaselineVisible) {
    4751                return false;
    4752            }
    4753            if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) {
    4754                return false;
    4755            }
    4756            if (this.domainCrosshairVisible != that.domainCrosshairVisible) {
    4757                return false;
    4758            }
    4759            if (this.domainCrosshairValue != that.domainCrosshairValue) {
    4760                return false;
    4761            }
    4762            if (this.domainCrosshairLockedOnData
    4763                    != that.domainCrosshairLockedOnData) {
    4764                return false;
    4765            }
    4766            if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) {
    4767                return false;
    4768            }
    4769            if (this.rangeCrosshairValue != that.rangeCrosshairValue) {
    4770                return false;
    4771            }
    4772            if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) {
    4773                return false;
    4774            }
    4775            if (!ObjectUtilities.equal(this.renderers, that.renderers)) {
    4776                return false;
    4777            }
    4778            if (!ObjectUtilities.equal(this.rangeAxes, that.rangeAxes)) {
    4779                return false;
    4780            }
    4781            if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) {
    4782                return false;
    4783            }
    4784            if (!ObjectUtilities.equal(this.datasetToDomainAxisMap,
    4785                    that.datasetToDomainAxisMap)) {
    4786                return false;
    4787            }
    4788            if (!ObjectUtilities.equal(this.datasetToRangeAxisMap,
    4789                    that.datasetToRangeAxisMap)) {
    4790                return false;
    4791            }
    4792            if (!ObjectUtilities.equal(this.domainGridlineStroke,
    4793                    that.domainGridlineStroke)) {
    4794                return false;
    4795            }
    4796            if (!PaintUtilities.equal(this.domainGridlinePaint,
    4797                    that.domainGridlinePaint)) {
    4798                return false;
    4799            }
    4800            if (!ObjectUtilities.equal(this.rangeGridlineStroke,
    4801                    that.rangeGridlineStroke)) {
    4802                return false;
    4803            }
    4804            if (!PaintUtilities.equal(this.rangeGridlinePaint,
    4805                    that.rangeGridlinePaint)) {
    4806                return false;
    4807            }
    4808            if (!PaintUtilities.equal(this.domainZeroBaselinePaint,
    4809                    that.domainZeroBaselinePaint)) {
    4810                return false;
    4811            }
    4812            if (!ObjectUtilities.equal(this.domainZeroBaselineStroke,
    4813                    that.domainZeroBaselineStroke)) {
    4814                return false;
    4815            }
    4816            if (!PaintUtilities.equal(this.rangeZeroBaselinePaint,
    4817                    that.rangeZeroBaselinePaint)) {
    4818                return false;
    4819            }
    4820            if (!ObjectUtilities.equal(this.rangeZeroBaselineStroke,
    4821                    that.rangeZeroBaselineStroke)) {
    4822                return false;
    4823            }
    4824            if (!ObjectUtilities.equal(this.domainCrosshairStroke,
    4825                    that.domainCrosshairStroke)) {
    4826                return false;
    4827            }
    4828            if (!PaintUtilities.equal(this.domainCrosshairPaint,
    4829                    that.domainCrosshairPaint)) {
    4830                return false;
    4831            }
    4832            if (!ObjectUtilities.equal(this.rangeCrosshairStroke,
    4833                    that.rangeCrosshairStroke)) {
    4834                return false;
    4835            }
    4836            if (!PaintUtilities.equal(this.rangeCrosshairPaint,
    4837                    that.rangeCrosshairPaint)) {
    4838                return false;
    4839            }
    4840            if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
    4841                    that.foregroundDomainMarkers)) {
    4842                return false;
    4843            }
    4844            if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
    4845                    that.backgroundDomainMarkers)) {
    4846                return false;
    4847            }
    4848            if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
    4849                    that.foregroundRangeMarkers)) {
    4850                return false;
    4851            }
    4852            if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
    4853                    that.backgroundRangeMarkers)) {
    4854                return false;
    4855            }
    4856            if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
    4857                    that.foregroundDomainMarkers)) {
    4858                return false;
    4859            }
    4860            if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
    4861                    that.backgroundDomainMarkers)) {
    4862                return false;
    4863            }
    4864            if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
    4865                    that.foregroundRangeMarkers)) {
    4866                return false;
    4867            }
    4868            if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
    4869                    that.backgroundRangeMarkers)) {
    4870                return false;
    4871            }
    4872            if (!ObjectUtilities.equal(this.annotations, that.annotations)) {
    4873                return false;
    4874            }
    4875            if (!PaintUtilities.equal(this.domainTickBandPaint,
    4876                    that.domainTickBandPaint)) {
    4877                return false;
    4878            }
    4879            if (!PaintUtilities.equal(this.rangeTickBandPaint,
    4880                    that.rangeTickBandPaint)) {
    4881                return false;
    4882            }
    4883            if (!this.quadrantOrigin.equals(that.quadrantOrigin)) {
    4884                return false;
    4885            }
    4886            for (int i = 0; i < 4; i++) {
    4887                if (!PaintUtilities.equal(this.quadrantPaint[i],
    4888                        that.quadrantPaint[i])) {
    4889                    return false;
    4890                }
    4891            }
    4892            return super.equals(obj);
    4893        }
    4894
    4895        /**
    4896         * Returns a clone of the plot.
    4897         *
    4898         * @return A clone.
    4899         *
    4900         * @throws CloneNotSupportedException  this can occur if some component of
    4901         *         the plot cannot be cloned.
    4902         */
    4903        public Object clone() throws CloneNotSupportedException {
    4904
    4905            XYPlot clone = (XYPlot) super.clone();
    4906            clone.domainAxes = (ObjectList) ObjectUtilities.clone(this.domainAxes);
    4907            for (int i = 0; i < this.domainAxes.size(); i++) {
    4908                ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
    4909                if (axis != null) {
    4910                    ValueAxis clonedAxis = (ValueAxis) axis.clone();
    4911                    clone.domainAxes.set(i, clonedAxis);
    4912                    clonedAxis.setPlot(clone);
    4913                    clonedAxis.addChangeListener(clone);
    4914                }
    4915            }
    4916            clone.domainAxisLocations = (ObjectList)
    4917                    this.domainAxisLocations.clone();
    4918
    4919            clone.rangeAxes = (ObjectList) ObjectUtilities.clone(this.rangeAxes);
    4920            for (int i = 0; i < this.rangeAxes.size(); i++) {
    4921                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
    4922                if (axis != null) {
    4923                    ValueAxis clonedAxis = (ValueAxis) axis.clone();
    4924                    clone.rangeAxes.set(i, clonedAxis);
    4925                    clonedAxis.setPlot(clone);
    4926                    clonedAxis.addChangeListener(clone);
    4927                }
    4928            }
    4929            clone.rangeAxisLocations = (ObjectList) ObjectUtilities.clone(
    4930                    this.rangeAxisLocations);
    4931
    4932            // the datasets are not cloned, but listeners need to be added...
    4933            clone.datasets = (ObjectList) ObjectUtilities.clone(this.datasets);
    4934            for (int i = 0; i < clone.datasets.size(); ++i) {
    4935                XYDataset d = getDataset(i);
    4936                if (d != null) {
    4937                    d.addChangeListener(clone);
    4938                }
    4939            }
    4940
    4941            clone.datasetToDomainAxisMap = new TreeMap();
    4942            clone.datasetToDomainAxisMap.putAll(this.datasetToDomainAxisMap);
    4943            clone.datasetToRangeAxisMap = new TreeMap();
    4944            clone.datasetToRangeAxisMap.putAll(this.datasetToRangeAxisMap);
    4945
    4946            clone.renderers = (ObjectList) ObjectUtilities.clone(this.renderers);
    4947            for (int i = 0; i < this.renderers.size(); i++) {
    4948                XYItemRenderer renderer2 = (XYItemRenderer) this.renderers.get(i);
    4949                if (renderer2 instanceof PublicCloneable) {
    4950                    PublicCloneable pc = (PublicCloneable) renderer2;
    4951                    clone.renderers.set(i, pc.clone());
    4952                }
    4953            }
    4954            clone.foregroundDomainMarkers = (Map) ObjectUtilities.clone(
    4955                    this.foregroundDomainMarkers);
    4956            clone.backgroundDomainMarkers = (Map) ObjectUtilities.clone(
    4957                    this.backgroundDomainMarkers);
    4958            clone.foregroundRangeMarkers = (Map) ObjectUtilities.clone(
    4959                    this.foregroundRangeMarkers);
    4960            clone.backgroundRangeMarkers = (Map) ObjectUtilities.clone(
    4961                    this.backgroundRangeMarkers);
    4962            clone.annotations = (List) ObjectUtilities.deepClone(this.annotations);
    4963            if (this.fixedDomainAxisSpace != null) {
    4964                clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtilities.clone(
    4965                        this.fixedDomainAxisSpace);
    4966            }
    4967            if (this.fixedRangeAxisSpace != null) {
    4968                clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtilities.clone(
    4969                        this.fixedRangeAxisSpace);
    4970            }
    4971
    4972            clone.quadrantOrigin = (Point2D) ObjectUtilities.clone(
    4973                    this.quadrantOrigin);
    4974            clone.quadrantPaint = (Paint[]) this.quadrantPaint.clone();
    4975            return clone;
    4976
    4977        }
    4978
    4979        /**
    4980         * Provides serialization support.
    4981         *
    4982         * @param stream  the output stream.
    4983         *
    4984         * @throws IOException  if there is an I/O error.
    4985         */
    4986        private void writeObject(ObjectOutputStream stream) throws IOException {
    4987            stream.defaultWriteObject();
    4988            SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
    4989            SerialUtilities.writePaint(this.domainGridlinePaint, stream);
    4990            SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
    4991            SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
    4992            SerialUtilities.writeStroke(this.rangeZeroBaselineStroke, stream);
    4993            SerialUtilities.writePaint(this.rangeZeroBaselinePaint, stream);
    4994            SerialUtilities.writeStroke(this.domainCrosshairStroke, stream);
    4995            SerialUtilities.writePaint(this.domainCrosshairPaint, stream);
    4996            SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream);
    4997            SerialUtilities.writePaint(this.rangeCrosshairPaint, stream);
    4998            SerialUtilities.writePaint(this.domainTickBandPaint, stream);
    4999            SerialUtilities.writePaint(this.rangeTickBandPaint, stream);
    5000            SerialUtilities.writePoint2D(this.quadrantOrigin, stream);
    5001            for (int i = 0; i < 4; i++) {
    5002                SerialUtilities.writePaint(this.quadrantPaint[i], stream);
    5003            }
    5004            SerialUtilities.writeStroke(this.domainZeroBaselineStroke, stream);
    5005            SerialUtilities.writePaint(this.domainZeroBaselinePaint, stream);
    5006        }
    5007
    5008        /**
    5009         * Provides serialization support.
    5010         *
    5011         * @param stream  the input stream.
    5012         *
    5013         * @throws IOException  if there is an I/O error.
    5014         * @throws ClassNotFoundException  if there is a classpath problem.
    5015         */
    5016        private void readObject(ObjectInputStream stream)
    5017            throws IOException, ClassNotFoundException {
    5018
    5019            stream.defaultReadObject();
    5020            this.domainGridlineStroke = SerialUtilities.readStroke(stream);
    5021            this.domainGridlinePaint = SerialUtilities.readPaint(stream);
    5022            this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
    5023            this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
    5024            this.rangeZeroBaselineStroke = SerialUtilities.readStroke(stream);
    5025            this.rangeZeroBaselinePaint = SerialUtilities.readPaint(stream);
    5026            this.domainCrosshairStroke = SerialUtilities.readStroke(stream);
    5027            this.domainCrosshairPaint = SerialUtilities.readPaint(stream);
    5028            this.rangeCrosshairStroke = SerialUtilities.readStroke(stream);
    5029            this.rangeCrosshairPaint = SerialUtilities.readPaint(stream);
    5030            this.domainTickBandPaint = SerialUtilities.readPaint(stream);
    5031            this.rangeTickBandPaint = SerialUtilities.readPaint(stream);
    5032            this.quadrantOrigin = SerialUtilities.readPoint2D(stream);
    5033            this.quadrantPaint = new Paint[4];
    5034            for (int i = 0; i < 4; i++) {
    5035                this.quadrantPaint[i] = SerialUtilities.readPaint(stream);
    5036            }
    5037
    5038            this.domainZeroBaselineStroke = SerialUtilities.readStroke(stream);
    5039            this.domainZeroBaselinePaint = SerialUtilities.readPaint(stream);
    5040
    5041            // register the plot as a listener with its axes, datasets, and
    5042            // renderers...
    5043            int domainAxisCount = this.domainAxes.size();
    5044            for (int i = 0; i < domainAxisCount; i++) {
    5045                Axis axis = (Axis) this.domainAxes.get(i);
    5046                if (axis != null) {
    5047                    axis.setPlot(this);
    5048                    axis.addChangeListener(this);
    5049                }
    5050            }
    5051            int rangeAxisCount = this.rangeAxes.size();
    5052            for (int i = 0; i < rangeAxisCount; i++) {
    5053                Axis axis = (Axis) this.rangeAxes.get(i);
    5054                if (axis != null) {
    5055                    axis.setPlot(this);
    5056                    axis.addChangeListener(this);
    5057                }
    5058            }
    5059            int datasetCount = this.datasets.size();
    5060            for (int i = 0; i < datasetCount; i++) {
    5061                Dataset dataset = (Dataset) this.datasets.get(i);
    5062                if (dataset != null) {
    5063                    dataset.addChangeListener(this);
    5064                }
    5065            }
    5066            int rendererCount = this.renderers.size();
    5067            for (int i = 0; i < rendererCount; i++) {
    5068                XYItemRenderer renderer = (XYItemRenderer) this.renderers.get(i);
    5069                if (renderer != null) {
    5070                    renderer.addChangeListener(this);
    5071                }
    5072            }
    5073
    5074        }
    5075
    5076    }