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