    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
    233package org.jfree.chart.plot;
    234
    235import java.awt.AlphaComposite;
    236import java.awt.BasicStroke;
    237import java.awt.Color;
    238import java.awt.Composite;
    239import java.awt.Graphics2D;
    240import java.awt.Paint;
    241import java.awt.Rectangle;
    242import java.awt.Shape;
    243import java.awt.Stroke;
    244import java.awt.geom.Line2D;
    245import java.awt.geom.Point2D;
    246import java.awt.geom.Rectangle2D;
    247import java.awt.image.BufferedImage;
    248import java.io.IOException;
    249import java.io.ObjectInputStream;
    250import java.io.ObjectOutputStream;
    251import java.io.Serializable;
    252import java.util.ArrayList;
    253import java.util.Collection;
    254import java.util.Collections;
    255import java.util.HashMap;
    256import java.util.HashSet;
    257import java.util.Iterator;
    258import java.util.List;
    259import java.util.Map;
    260import java.util.ResourceBundle;
    261import java.util.Set;
    262import java.util.TreeMap;
    263
    264import org.jfree.chart.LegendItem;
    265import org.jfree.chart.LegendItemCollection;
    266import org.jfree.chart.annotations.Annotation;
    267import org.jfree.chart.annotations.XYAnnotation;
    268import org.jfree.chart.annotations.XYAnnotationBoundsInfo;
    269import org.jfree.chart.axis.Axis;
    270import org.jfree.chart.axis.AxisCollection;
    271import org.jfree.chart.axis.AxisLocation;
    272import org.jfree.chart.axis.AxisSpace;
    273import org.jfree.chart.axis.AxisState;
    274import org.jfree.chart.axis.TickType;
    275import org.jfree.chart.axis.ValueAxis;
    276import org.jfree.chart.axis.ValueTick;
    277import org.jfree.chart.event.AnnotationChangeEvent;
    278import org.jfree.chart.event.ChartChangeEventType;
    279import org.jfree.chart.event.PlotChangeEvent;
    280import org.jfree.chart.event.RendererChangeEvent;
    281import org.jfree.chart.event.RendererChangeListener;
    282import org.jfree.chart.renderer.RendererUtilities;
    283import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
    284import org.jfree.chart.renderer.xy.XYItemRenderer;
    285import org.jfree.chart.renderer.xy.XYItemRendererState;
    286import org.jfree.chart.util.ResourceBundleWrapper;
    287import org.jfree.chart.util.ShadowGenerator;
    288import org.jfree.data.Range;
    289import org.jfree.data.general.Dataset;
    290import org.jfree.data.general.DatasetChangeEvent;
    291import org.jfree.data.general.DatasetUtilities;
    292import org.jfree.data.xy.XYDataset;
    293import org.jfree.io.SerialUtilities;
    294import org.jfree.ui.Layer;
    295import org.jfree.ui.RectangleEdge;
    296import org.jfree.ui.RectangleInsets;
    297import org.jfree.util.ObjectList;
    298import org.jfree.util.ObjectUtilities;
    299import org.jfree.util.PaintUtilities;
    300import 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 */
    313public 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}