content: /* ===========================================================
   * JFreeChart : a free chart library for the Java(tm) platform
   * ===========================================================
   *
   * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
   *
   * Project Info:  http://www.jfree.org/jfreechart/index.html
   *
   * This library is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   *
   * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
   * Other names may be trademarks of their respective owners.]
   *
   * -----------
   * XYPlot.java
   * -----------
   * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
   *
   * Original Author:  David Gilbert (for Object Refinery Limited);
   * Contributor(s):   Craig MacFarlane;
   *                   Mark Watson (www.markwatson.com);
   *                   Jonathan Nash;
   *                   Gideon Krause;
   *                   Klaus Rheinwald;
   *                   Xavier Poinsard;
   *                   Richard Atkinson;
   *                   Arnaud Lelievre;
   *                   Nicolas Brodu;
   *                   Eduardo Ramalho;
   *                   Sergei Ivanov;
   *                   Richard West, Advanced Micro Devices, Inc.;
   *                   Ulrich Voigt - patches 1997549 and 2686040;
   *                   Peter Kolb - patches 1934255, 2603321 and 2809117;
   *                   Andrew Mickish - patch 1868749;
   *
   * Changes (from 21-Jun-2001)
   * --------------------------
   * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
   * 18-Sep-2001 : Updated header and fixed DOS encoding problem (DG);
   * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG);
   * 19-Oct-2001 : Removed the code for drawing the visual representation of each
   *               data point into a separate class StandardXYItemRenderer.
   *               This will make it easier to add variations to the way the
   *               charts are drawn.  Based on code contributed by Mark
   *               Watson (DG);
   * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
   * 20-Nov-2001 : Fixed clipping bug that shows up when chart is displayed
   *               inside JScrollPane (DG);
   * 12-Dec-2001 : Removed unnecessary 'throws' clauses from constructor (DG);
   * 13-Dec-2001 : Added skeleton code for tooltips.  Added new constructor. (DG);
   * 16-Jan-2002 : Renamed the tooltips class (DG);
   * 22-Jan-2002 : Added DrawInfo class, incorporating tooltips and crosshairs.
   *               Crosshairs based on code by Jonathan Nash (DG);
   * 05-Feb-2002 : Added alpha-transparency setting based on code by Sylvain
   *               Vieujot (DG);
   * 26-Feb-2002 : Updated getMinimumXXX() and getMaximumXXX() methods to handle
   *               special case when chart is null (DG);
   * 28-Feb-2002 : Renamed Datasets.java --> DatasetUtilities.java (DG);
   * 28-Mar-2002 : The plot now registers with the renderer as a property change
   *               listener.  Also added a new constructor (DG);
   * 09-Apr-2002 : Removed the transRangeZero from the renderer.drawItem()
   *               method.  Moved the tooltip generator into the renderer (DG);
   * 23-Apr-2002 : Fixed bug in methods for drawing horizontal and vertical
   *               lines (DG);
   * 13-May-2002 : Small change to the draw() method so that it works for
   *               OverlaidXYPlot also (DG);
   * 25-Jun-2002 : Removed redundant import (DG);
   * 20-Aug-2002 : Renamed getItemRenderer() --> getRenderer(), and
   *               setXYItemRenderer() --> setRenderer() (DG);
   * 28-Aug-2002 : Added mechanism for (optional) plot annotations (DG);
   * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
   * 18-Nov-2002 : Added grid settings for both domain and range axis (previously
   *               these were set in the axes) (DG);
   * 09-Jan-2003 : Further additions to the grid settings, plus integrated plot
   *               border bug fix contributed by Gideon Krause (DG);
   * 22-Jan-2003 : Removed monolithic constructor (DG);
   * 04-Mar-2003 : Added 'no data' message, see bug report 691634.  Added
   *               secondary range markers using code contributed by Klaus
   *               Rheinwald (DG);
   * 26-Mar-2003 : Implemented Serializable (DG);
   * 03-Apr-2003 : Added setDomainAxisLocation() method (DG);
   * 30-Apr-2003 : Moved annotation drawing into a separate method (DG);
   * 01-May-2003 : Added multi-pass mechanism for renderers (DG);
   * 02-May-2003 : Changed axis locations from int to AxisLocation (DG);
   * 15-May-2003 : Added an orientation attribute (DG);
   * 02-Jun-2003 : Removed range axis compatibility test (DG);
   * 05-Jun-2003 : Added domain and range grid bands (sponsored by Focus Computer
   *               Services Ltd) (DG);
   * 26-Jun-2003 : Fixed bug (757303) in getDataRange() method (DG);
   * 02-Jul-2003 : Added patch from bug report 698646 (secondary axes for
   *               overlaid plots) (DG);
   * 23-Jul-2003 : Added support for multiple secondary datasets, axes and
   *               renderers (DG);
   * 27-Jul-2003 : Added support for stacked XY area charts (RA);
   * 19-Aug-2003 : Implemented Cloneable (DG);
   * 01-Sep-2003 : Fixed bug where change to secondary datasets didn't generate
   *               change event (797466) (DG)
   * 08-Sep-2003 : Added internationalization via use of properties
   *               resourceBundle (RFE 690236) (AL);
   * 08-Sep-2003 : Changed ValueAxis API (DG);
   * 08-Sep-2003 : Fixes for serialization (NB);
   * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
   * 17-Sep-2003 : Fixed zooming to include secondary domain axes (DG);
   * 18-Sep-2003 : Added getSecondaryDomainAxisCount() and
   *               getSecondaryRangeAxisCount() methods suggested by Eduardo
   *               Ramalho (RFE 808548) (DG);
   * 23-Sep-2003 : Split domain and range markers into foreground and
   *               background (DG);
   * 06-Oct-2003 : Fixed bug in clearDomainMarkers() and clearRangeMarkers()
   *               methods.  Fixed bug (815876) in addSecondaryRangeMarker()
   *               method.  Added new addSecondaryDomainMarker methods (see bug
   *               id 815869) (DG);
   * 10-Nov-2003 : Added getSecondaryDomain/RangeAxisMappedToDataset() methods
   *               requested by Eduardo Ramalho (DG);
   * 24-Nov-2003 : Removed unnecessary notification when updating axis anchor
   *               values (DG);
   * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
   * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
   * 12-Mar-2004 : Fixed bug where primary renderer is always used to determine
   *               range type (DG);
   * 22-Mar-2004 : Fixed cloning bug (DG);
   * 23-Mar-2004 : Fixed more cloning bugs (DG);
   * 07-Apr-2004 : Fixed problem with axis range when the secondary renderer is
   *               stacked, see this post in the forum:
   *               http://www.jfree.org/phpBB2/viewtopic.php?t=8204 (DG);
   * 07-Apr-2004 : Added get/setDatasetRenderingOrder() methods (DG);
   * 26-Apr-2004 : Added option to fill quadrant areas in the background of the
   *               plot (DG);
   * 27-Apr-2004 : Removed major distinction between primary and secondary
   *               datasets, renderers and axes (DG);
   * 30-Apr-2004 : Modified to make use of the new getRangeExtent() method in the
   *               renderer interface (DG);
   * 13-May-2004 : Added optional fixedLegendItems attribute (DG);
   * 19-May-2004 : Added indexOf() method (DG);
   * 03-Jun-2004 : Fixed zooming bug (DG);
   * 18-Aug-2004 : Added removedAnnotation() method (by tkram01) (DG);
   * 05-Oct-2004 : Modified storage type for dataset-to-axis maps (DG);
   * 06-Oct-2004 : Modified getDataRange() method to use renderer to determine
   *               the x-value range (now matches behaviour for y-values).  Added
   *               getDomainAxisIndex() method (DG);
   * 12-Nov-2004 : Implemented new Zoomable interface (DG);
   * 25-Nov-2004 : Small update to clone() implementation (DG);
   * 22-Feb-2005 : Changed axis offsets from Spacer --> RectangleInsets (DG);
   * 24-Feb-2005 : Added indexOf(XYItemRenderer) method (DG);
   * 21-Mar-2005 : Register plot as change listener in setRenderer() method (DG);
   * 21-Apr-2005 : Added get/setSeriesRenderingOrder() methods (ET);
   * 26-Apr-2005 : Removed LOGGER (DG);
   * 04-May-2005 : Fixed serialization of domain and range markers (DG);
   * 05-May-2005 : Removed unused draw() method (DG);
   * 20-May-2005 : Added setDomainAxes() and setRangeAxes() methods, as per
   *               RFE 1183100 (DG);
   * 01-Jun-2005 : Upon deserialization, register plot as a listener with its
   *               axes, dataset(s) and renderer(s) - see patch 1209475 (DG);
   * 01-Jun-2005 : Added clearDomainMarkers(int) method to match
   *               clearRangeMarkers(int) (DG);
   * 06-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
   * 09-Jun-2005 : Added setRenderers(), as per RFE 1183100 (DG);
   * 06-Jul-2005 : Fixed crosshair bug (id = 1233336) (DG);
   * ------------- JFREECHART 1.0.x ---------------------------------------------
   * 26-Jan-2006 : Added getAnnotations() method (DG);
   * 05-Sep-2006 : Added MarkerChangeEvent support (DG);
   * 13-Oct-2006 : Fixed initialisation of CrosshairState - see bug report
   *               1565168 (DG);
   * 22-Nov-2006 : Fixed equals() and cloning() for quadrant attributes, plus
   *               API doc updates (DG);
   * 29-Nov-2006 : Added argument checks (DG);
   * 15-Jan-2007 : Fixed bug in drawRangeMarkers() (DG);
   * 07-Feb-2007 : Fixed bug 1654215, renderer with no dataset (DG);
   * 26-Feb-2007 : Added missing setDomainAxisLocation() and
   *               setRangeAxisLocation() methods (DG);
   * 02-Mar-2007 : Fix for crosshair positioning with horizontal orientation
   *               (see patch 1671648 by Sergei Ivanov) (DG);
   * 13-Mar-2007 : Added null argument checks for crosshair attributes (DG);
   * 23-Mar-2007 : Added domain zero base line facility (DG);
   * 04-May-2007 : Render only visible data items if possible (DG);
   * 24-May-2007 : Fixed bug in render method for an empty series (DG);
   * 07-Jun-2007 : Modified drawBackground() to pass orientation to
   *               fillBackground() for handling GradientPaint (DG);
   * 24-Sep-2007 : Added new zoom methods (DG);
   * 26-Sep-2007 : Include index value in IllegalArgumentExceptions (DG);
   * 05-Nov-2007 : Applied patch 1823697, by Richard West, for removal of domain
   *               and range markers (DG);
   * 12-Nov-2007 : Fixed bug in equals() method for domain and range tick
   *               band paint attributes (DG);
   * 27-Nov-2007 : Added new setFixedDomain/RangeAxisSpace() methods (DG);
   * 04-Jan-2008 : Fix for quadrant painting error - see patch 1849564 (DG);
   * 25-Mar-2008 : Added new methods with optional notification - see patch
   *               1913751 (DG);
   * 07-Apr-2008 : Fixed NPE in removeDomainMarker() and
   *               removeRangeMarker() (DG);
   * 22-May-2008 : Modified calculateAxisSpace() to process range axes first,
   *               then adjust the plot area before calculating the space
   *               for the domain axes (DG);
   * 09-Jul-2008 : Added renderer state notification when series pass begins
   *               and ends - see patch 1997549 by Ulrich Voigt (DG);
   * 25-Jul-2008 : Fixed NullPointerException for plots with no axes (DG);
   * 15-Aug-2008 : Added getRendererCount() method (DG);
   * 25-Sep-2008 : Added minor tick support, see patch 1934255 by Peter Kolb (DG);
   * 25-Nov-2008 : Allow datasets to be mapped to multiple axes - based on patch
   *               1868749 by Andrew Mickish (DG);
   * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
   *               Jess Thrysoee (DG);
   * 10-Mar-2009 : Allow some annotations to contribute to axis autoRange (DG);
   * 18-Mar-2009 : Modified anchored zoom behaviour and fixed bug in
   *               "process visible range" rendering (DG);
   * 19-Mar-2009 : Added panning support based on patch 2686040 by Ulrich
   *               Voigt (DG);
   * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG);
   * 30-Mar-2009 : Delegate panning to axes (DG);
   * 10-May-2009 : Added check for fixedLegendItems in equals(), and code to
   *               handle cloning (DG);
   * 24-Jun-2009 : Added support for annotation events - see patch 2809117
   *               by PK (DG);
   * 06-Jul-2009 : Fix for cloning of renderers - see bug 2817504 (DG)
   * 10-Jul-2009 : Added optional drop shadow generator (DG);
   * 18-Oct-2011 : Fix tooltip offset with shadow renderer (DG);
   * 15-Jun-2012 : Removed JCommon dependencies (DG);
   *
   */
  
  package org.jfree.chart.plot;
  
  import java.awt.AlphaComposite;
  import java.awt.BasicStroke;
  import java.awt.Color;
  import java.awt.Composite;
  import java.awt.Graphics2D;
  import java.awt.Paint;
  import java.awt.Rectangle;
  import java.awt.Shape;
  import java.awt.Stroke;
  import java.awt.geom.Line2D;
  import java.awt.geom.Point2D;
  import java.awt.geom.Rectangle2D;
  import java.awt.image.BufferedImage;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.ResourceBundle;
  import java.util.Set;
  import java.util.TreeMap;
  
  import org.jfree.chart.LegendItem;
  import org.jfree.chart.annotations.Annotation;
  import org.jfree.chart.annotations.XYAnnotation;
  import org.jfree.chart.annotations.XYAnnotationBoundsInfo;
  import org.jfree.chart.axis.Axis;
  import org.jfree.chart.axis.AxisCollection;
  import org.jfree.chart.axis.AxisLocation;
  import org.jfree.chart.axis.AxisSpace;
  import org.jfree.chart.axis.AxisState;
  import org.jfree.chart.axis.TickType;
  import org.jfree.chart.axis.ValueAxis;
  import org.jfree.chart.axis.ValueTick;
  import org.jfree.chart.ui.Layer;
  import org.jfree.chart.ui.RectangleEdge;
  import org.jfree.chart.ui.RectangleInsets;
  import org.jfree.chart.util.ObjectList;
  import org.jfree.chart.util.ObjectUtilities;
  import org.jfree.chart.util.PaintUtilities;
  import org.jfree.chart.util.PublicCloneable;
  import org.jfree.chart.event.AnnotationChangeEvent;
  import org.jfree.chart.event.ChartChangeEventType;
  import org.jfree.chart.event.PlotChangeEvent;
  import org.jfree.chart.event.RendererChangeEvent;
  import org.jfree.chart.event.RendererChangeListener;
  import org.jfree.chart.renderer.RendererUtilities;
  import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
  import org.jfree.chart.renderer.xy.XYItemRenderer;
  import org.jfree.chart.renderer.xy.XYItemRendererState;
  import org.jfree.chart.util.ParamChecks;
  import org.jfree.chart.util.ResourceBundleWrapper;
  import org.jfree.chart.util.SerialUtilities;
  import org.jfree.chart.util.ShadowGenerator;
  import org.jfree.data.Range;
  import org.jfree.data.general.Dataset;
  import org.jfree.data.general.DatasetChangeEvent;
  import org.jfree.data.general.DatasetUtilities;
  import org.jfree.data.xy.CategoryTableXYDataset;
  import org.jfree.data.xy.XYDataset;
  
  /**
   * A general class for plotting data in the form of (x, y) pairs.  This plot can
   * use data from any class that implements the {@link XYDataset} interface.
   * <P>
   * <code>XYPlot</code> makes use of an {@link XYItemRenderer} to draw each point
   * on the plot.  By using different renderers, various chart types can be
   * produced.
   * <p>
   * The {@link org.jfree.chart.ChartFactory} class contains static methods for
   * creating pre-configured charts.
   */
  public class XYPlot extends Plot implements ValueAxisPlot, Pannable, Zoomable,
          RendererChangeListener, Cloneable, PublicCloneable, Serializable {
  
      /** For serialization. */
      private static final long serialVersionUID = 7044148245716569264L;
  
      /** The default grid line stroke. */
      public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
              BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f,
              new float[] {2.0f, 2.0f}, 0.0f);
  
      /** The default grid line paint. */
      public static final Paint DEFAULT_GRIDLINE_PAINT = Color.LIGHT_GRAY;
  
      /** The default crosshair visibility. */
      public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
  
      /** The default crosshair stroke. */
      public static final Stroke DEFAULT_CROSSHAIR_STROKE
              = DEFAULT_GRIDLINE_STROKE;
  
      /** The default crosshair paint. */
      public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.BLUE;
  
      /** The resourceBundle for the localization. */
      protected static ResourceBundle localizationResources
              = ResourceBundleWrapper.getBundle(
                      "org.jfree.chart.plot.LocalizationBundle");
  
      /** The plot orientation. */
      private PlotOrientation orientation;
  
      /** The offset between the data area and the axes. */
      private RectangleInsets axisOffset;
  
      /** The domain axis / axes (used for the x-values). */
      private ObjectList<ValueAxis> domainAxes;
  
      /** The domain axis locations. */
      private ObjectList<AxisLocation> domainAxisLocations;
  
      /** The range axis (used for the y-values). */
      private ObjectList<ValueAxis> rangeAxes;
  
      /** The range axis location. */
      private ObjectList<AxisLocation> rangeAxisLocations;
  
      /** Storage for the datasets. */
      private ObjectList<XYDataset> datasets;
  
      /** Storage for the renderers. */
      private ObjectList<XYItemRenderer> renderers;
  
      /**
       * Storage for the mapping between datasets/renderers and domain axes.  The
       * keys in the map are Integer objects, corresponding to the dataset
       * index.  The values in the map are List objects containing Integer
       * objects (corresponding to the axis indices).  If the map contains no
       * entry for a dataset, it is assumed to map to the primary domain axis
       * (index = 0).
       */
      private Map<Integer, List<Integer>> datasetToDomainAxesMap;
  
      /**
       * Storage for the mapping between datasets/renderers and range axes.  The
       * keys in the map are Integer objects, corresponding to the dataset
       * index.  The values in the map are List objects containing Integer
       * objects (corresponding to the axis indices).  If the map contains no
       * entry for a dataset, it is assumed to map to the primary domain axis
       * (index = 0).
       */
      private Map<Integer, List<Integer>> datasetToRangeAxesMap;
  
      /** The origin point for the quadrants (if drawn). */
      private transient Point2D quadrantOrigin = new Point2D.Double(0.0, 0.0);
  
      /** The paint used for each quadrant. */
      private transient Paint[] quadrantPaint
              = new Paint[] {null, null, null, null};
  
      /** A flag that controls whether the domain grid-lines are visible. */
      private boolean domainGridlinesVisible;
  
      /** The stroke used to draw the domain grid-lines. */
      private transient Stroke domainGridlineStroke;
  
      /** The paint used to draw the domain grid-lines. */
      private transient Paint domainGridlinePaint;
  
      /** A flag that controls whether the range grid-lines are visible. */
      private boolean rangeGridlinesVisible;
  
      /** The stroke used to draw the range grid-lines. */
      private transient Stroke rangeGridlineStroke;
  
      /** The paint used to draw the range grid-lines. */
      private transient Paint rangeGridlinePaint;
  
      /**
       * A flag that controls whether the domain minor grid-lines are visible.
       *
       * @since 1.0.12
       */
      private boolean domainMinorGridlinesVisible;
  
      /**
       * The stroke used to draw the domain minor grid-lines.
       *
       * @since 1.0.12
       */
      private transient Stroke domainMinorGridlineStroke;
  
      /**
       * The paint used to draw the domain minor grid-lines.
       *
       * @since 1.0.12
       */
      private transient Paint domainMinorGridlinePaint;
  
      /**
       * A flag that controls whether the range minor grid-lines are visible.
       *
       * @since 1.0.12
       */
      private boolean rangeMinorGridlinesVisible;
  
      /**
       * The stroke used to draw the range minor grid-lines.
       *
       * @since 1.0.12
       */
      private transient Stroke rangeMinorGridlineStroke;
  
      /**
       * The paint used to draw the range minor grid-lines.
       *
       * @since 1.0.12
       */
      private transient Paint rangeMinorGridlinePaint;
  
      /**
       * A flag that controls whether or not the zero baseline against the domain
       * axis is visible.
       *
       * @since 1.0.5
       */
      private boolean domainZeroBaselineVisible;
  
      /**
       * The stroke used for the zero baseline against the domain axis.
       *
       * @since 1.0.5
       */
      private transient Stroke domainZeroBaselineStroke;
  
      /**
       * The paint used for the zero baseline against the domain axis.
       *
       * @since 1.0.5
       */
      private transient Paint domainZeroBaselinePaint;
  
      /**
       * A flag that controls whether or not the zero baseline against the range
       * axis is visible.
       */
      private boolean rangeZeroBaselineVisible;
  
      /** The stroke used for the zero baseline against the range axis. */
      private transient Stroke rangeZeroBaselineStroke;
  
      /** The paint used for the zero baseline against the range axis. */
      private transient Paint rangeZeroBaselinePaint;
  
      /** A flag that controls whether or not a domain crosshair is drawn..*/
      private boolean domainCrosshairVisible;
  
      /** The domain crosshair value. */
      private double domainCrosshairValue;
  
      /** The pen/brush used to draw the crosshair (if any). */
      private transient Stroke domainCrosshairStroke;
  
      /** The color used to draw the crosshair (if any). */
      private transient Paint domainCrosshairPaint;
  
      /**
       * A flag that controls whether or not the crosshair locks onto actual
       * data points.
       */
      private boolean domainCrosshairLockedOnData = true;
  
      /** A flag that controls whether or not a range crosshair is drawn..*/
      private boolean rangeCrosshairVisible;
  
      /** The range crosshair value. */
      private double rangeCrosshairValue;
  
      /** The pen/brush used to draw the crosshair (if any). */
      private transient Stroke rangeCrosshairStroke;
  
      /** The color used to draw the crosshair (if any). */
      private transient Paint rangeCrosshairPaint;
  
      /**
       * A flag that controls whether or not the crosshair locks onto actual
       * data points.
       */
      private boolean rangeCrosshairLockedOnData = true;
  
      /** A map of lists of foreground markers (optional) for the domain axes. */
      private Map<Integer, Collection<Marker>> foregroundDomainMarkers;
  
      /** A map of lists of background markers (optional) for the domain axes. */
      private Map<Integer, Collection<Marker>> backgroundDomainMarkers;
  
      /** A map of lists of foreground markers (optional) for the range axes. */
      private Map<Integer, Collection<Marker>> foregroundRangeMarkers;
  
      /** A map of lists of background markers (optional) for the range axes. */
      private Map<Integer, Collection<Marker>> backgroundRangeMarkers;
  
      /**
       * A (possibly empty) list of annotations for the plot.  The list should
       * be initialised in the constructor and never allowed to be
       * <code>null</code>.
       */
      private List<XYAnnotation> annotations;
  
      /** The paint used for the domain tick bands (if any). */
      private transient Paint domainTickBandPaint;
  
      /** The paint used for the range tick bands (if any). */
      private transient Paint rangeTickBandPaint;
  
      /** The fixed domain axis space. */
      private AxisSpace fixedDomainAxisSpace;
  
      /** The fixed range axis space. */
      private AxisSpace fixedRangeAxisSpace;
  
      /**
       * The order of the dataset rendering (REVERSE draws the primary dataset
       * last so that it appears to be on top).
       */
      private DatasetRenderingOrder datasetRenderingOrder
              = DatasetRenderingOrder.REVERSE;
  
      /**
       * The order of the series rendering (REVERSE draws the primary series
       * last so that it appears to be on top).
       */
      private SeriesRenderingOrder seriesRenderingOrder
              = SeriesRenderingOrder.REVERSE;
  
      /**
       * The weight for this plot (only relevant if this is a subplot in a
       * combined plot).
       */
      private int weight;
  
      /**
       * An optional collection of legend items that can be returned by the
       * getLegendItems() method.
       */
      private List<LegendItem> fixedLegendItems;
  
      /**
       * A flag that controls whether or not panning is enabled for the domain
       * axis/axes.
       *
       * @since 1.0.13
       */
      private boolean domainPannable;
  
      /**
       * A flag that controls whether or not panning is enabled for the range
       * axis/axes.
       *
       * @since 1.0.13
       */
      private boolean rangePannable;
  
      /**
       * The shadow generator (<code>null</code> permitted).
       *
       * @since 1.0.14
       */
      private ShadowGenerator shadowGenerator;
  
      /**
       * Creates a new <code>XYPlot</code> instance with no dataset, no axes and
       * no renderer.  You should specify these items before using the plot.
       */
      public XYPlot() {
          this(null, null, null, null);
      }
  
      /**
       * Creates a new plot with the specified dataset, axes and renderer.  Any
       * of the arguments can be <code>null</code>, but in that case you should
       * take care to specify the value before using the plot (otherwise a
       * <code>NullPointerException</code> may be thrown).
       *
       * @param dataset  the dataset (<code>null</code> permitted).
       * @param domainAxis  the domain axis (<code>null</code> permitted).
       * @param rangeAxis  the range axis (<code>null</code> permitted).
       * @param renderer  the renderer (<code>null</code> permitted).
       */
      public XYPlot(XYDataset dataset,
                    ValueAxis domainAxis,
                    ValueAxis rangeAxis,
                    XYItemRenderer renderer) {
  
          super();
  
          this.orientation = PlotOrientation.VERTICAL;
          this.weight = 1;  // only relevant when this is a subplot
          this.axisOffset = RectangleInsets.ZERO_INSETS;
  
          // allocate storage for datasets, axes and renderers (all optional)
          this.domainAxes = new ObjectList<ValueAxis>();
          this.domainAxisLocations = new ObjectList<AxisLocation>();
          this.foregroundDomainMarkers = new HashMap<Integer, Collection<Marker>>();
          this.backgroundDomainMarkers = new HashMap<Integer, Collection<Marker>>();
  
          this.rangeAxes = new ObjectList<ValueAxis>();
          this.rangeAxisLocations = new ObjectList<AxisLocation>();
          this.foregroundRangeMarkers = new HashMap<Integer, Collection<Marker>>();
          this.backgroundRangeMarkers = new HashMap<Integer, Collection<Marker>>();
  
          this.datasets = new ObjectList<XYDataset>();
          this.renderers = new ObjectList<XYItemRenderer>();
  
          this.datasetToDomainAxesMap = new TreeMap<Integer, List<Integer>>();
          this.datasetToRangeAxesMap = new TreeMap<Integer, List<Integer>>();
  
          this.annotations = new java.util.ArrayList<XYAnnotation>();
  
          this.datasets.set(0, dataset);
          if (dataset != null) {
              dataset.addChangeListener(this);
          }
  
          this.renderers.set(0, renderer);
          if (renderer != null) {
              renderer.setPlot(this);
              renderer.addChangeListener(this);
          }
  
          this.domainAxes.set(0, domainAxis);
          this.mapDatasetToDomainAxis(0, 0);
          if (domainAxis != null) {
              domainAxis.setPlot(this);
              domainAxis.addChangeListener(this);
          }
          this.domainAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT);
  
          this.rangeAxes.set(0, rangeAxis);
          this.mapDatasetToRangeAxis(0, 0);
          if (rangeAxis != null) {
              rangeAxis.setPlot(this);
              rangeAxis.addChangeListener(this);
          }
          this.rangeAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT);
  
          configureDomainAxes();
          configureRangeAxes();
  
          this.domainGridlinesVisible = true;
          this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
          this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
  
          this.domainMinorGridlinesVisible = false;
          this.domainMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE;
          this.domainMinorGridlinePaint = Color.WHITE;
  
          this.domainZeroBaselineVisible = false;
          this.domainZeroBaselinePaint = Color.BLACK;
          this.domainZeroBaselineStroke = new BasicStroke(0.5f);
  
          this.rangeGridlinesVisible = true;
          this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
          this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
  
          this.rangeMinorGridlinesVisible = false;
          this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE;
          this.rangeMinorGridlinePaint = Color.WHITE;
  
          this.rangeZeroBaselineVisible = false;
          this.rangeZeroBaselinePaint = Color.BLACK;
          this.rangeZeroBaselineStroke = new BasicStroke(0.5f);
  
          this.domainCrosshairVisible = false;
          this.domainCrosshairValue = 0.0;
          this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
          this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
  
          this.rangeCrosshairVisible = false;
          this.rangeCrosshairValue = 0.0;
          this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
          this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
          this.shadowGenerator = null;
      }
  
      /**
       * Returns the plot type as a string.
       *
       * @return A short string describing the type of plot.
       */
      @Override
      public String getPlotType() {
          return localizationResources.getString("XY_Plot");
      }
  
      /**
       * Returns the orientation of the plot.
       *
       * @return The orientation (never <code>null</code>).
       *
       * @see #setOrientation(PlotOrientation)
       */
      @Override
      public PlotOrientation getOrientation() {
          return this.orientation;
      }
  
      /**
       * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param orientation  the orientation (<code>null</code> not allowed).
       *
       * @see #getOrientation()
       */
      public void setOrientation(PlotOrientation orientation) {
          ParamChecks.nullNotPermitted(orientation, "orientation");
          if (orientation != this.orientation) {
              this.orientation = orientation;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the axis offset.
       *
       * @return The axis offset (never <code>null</code>).
       *
       * @see #setAxisOffset(RectangleInsets)
       */
      public RectangleInsets getAxisOffset() {
          return this.axisOffset;
      }
  
      /**
       * Sets the axis offsets (gap between the data area and the axes) and sends
       * a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param offset  the offset (<code>null</code> not permitted).
       *
       * @see #getAxisOffset()
       */
      public void setAxisOffset(RectangleInsets offset) {
          if (offset == null) {
              throw new IllegalArgumentException("Null 'offset' argument.");
          }
          this.axisOffset = offset;
          fireChangeEvent();
      }
  
      /**
       * Returns the domain axis with index 0.  If the domain axis for this plot
       * is <code>null</code>, then the method will return the parent plot's
       * domain axis (if there is a parent plot).
       *
       * @return The domain axis (possibly <code>null</code>).
       *
       * @see #getDomainAxis(int)
       * @see #setDomainAxis(ValueAxis)
       */
      public ValueAxis getDomainAxis() {
          return getDomainAxis(0);
      }
  
      /**
       * Returns the domain axis with the specified index, or <code>null</code>.
       *
       * @param index  the axis index.
       *
       * @return The axis (<code>null</code> possible).
       *
       * @see #setDomainAxis(int, ValueAxis)
       */
      public ValueAxis getDomainAxis(int index) {
          ValueAxis result = null;
          if (index < this.domainAxes.size()) {
              result = this.domainAxes.get(index);
          }
          if (result == null) {
              Plot parent = getParent();
              if (parent instanceof XYPlot) {
                  XYPlot xy = (XYPlot) parent;
                  result = xy.getDomainAxis(index);
              }
          }
          return result;
      }
  
      /**
       * Sets the domain axis for the plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param axis  the new axis (<code>null</code> permitted).
       *
       * @see #getDomainAxis()
       * @see #setDomainAxis(int, ValueAxis)
       */
      public void setDomainAxis(ValueAxis axis) {
          setDomainAxis(0, axis);
      }
  
      /**
       * Sets a domain axis and sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @param index  the axis index.
       * @param axis  the axis (<code>null</code> permitted).
       *
       * @see #getDomainAxis(int)
       * @see #setRangeAxis(int, ValueAxis)
       */
      public void setDomainAxis(int index, ValueAxis axis) {
          setDomainAxis(index, axis, true);
      }
  
      /**
       * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param index  the axis index.
       * @param axis  the axis.
       * @param notify  notify listeners?
       *
       * @see #getDomainAxis(int)
       */
      public void setDomainAxis(int index, ValueAxis axis, boolean notify) {
          ValueAxis existing = getDomainAxis(index);
          if (existing != null) {
              existing.removeChangeListener(this);
          }
          if (axis != null) {
              axis.setPlot(this);
          }
          this.domainAxes.set(index, axis);
          if (axis != null) {
              axis.configure();
              axis.addChangeListener(this);
          }
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param axes  the axes (<code>null</code> not permitted).
       *
       * @see #setRangeAxes(ValueAxis[])
       */
      public void setDomainAxes(ValueAxis[] axes) {
          for (int i = 0; i < axes.length; i++) {
              setDomainAxis(i, axes[i], false);
          }
          fireChangeEvent();
      }
  
      /**
       * Returns the location of the primary domain axis.
       *
       * @return The location (never <code>null</code>).
       *
       * @see #setDomainAxisLocation(AxisLocation)
       */
      public AxisLocation getDomainAxisLocation() {
          return this.domainAxisLocations.get(0);
      }
  
      /**
       * Sets the location of the primary domain axis and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param location  the location (<code>null</code> not permitted).
       *
       * @see #getDomainAxisLocation()
       */
      public void setDomainAxisLocation(AxisLocation location) {
          // delegate...
          setDomainAxisLocation(0, location, true);
      }
  
      /**
       * Sets the location of the domain axis and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param location  the location (<code>null</code> not permitted).
       * @param notify  notify listeners?
       *
       * @see #getDomainAxisLocation()
       */
      public void setDomainAxisLocation(AxisLocation location, boolean notify) {
          // delegate...
          setDomainAxisLocation(0, location, notify);
      }
  
      /**
       * Returns the edge for the primary domain axis (taking into account the
       * plot's orientation).
       *
       * @return The edge.
       *
       * @see #getDomainAxisLocation()
       * @see #getOrientation()
       */
      public RectangleEdge getDomainAxisEdge() {
          return Plot.resolveDomainAxisLocation(getDomainAxisLocation(),
                  this.orientation);
      }
  
      /**
       * Returns the number of domain axes.
       *
       * @return The axis count.
       *
       * @see #getRangeAxisCount()
       */
      public int getDomainAxisCount() {
          return this.domainAxes.size();
      }
  
      /**
       * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @see #clearRangeAxes()
       */
      public void clearDomainAxes() {
          for (int i = 0; i < this.domainAxes.size(); i++) {
              ValueAxis axis = this.domainAxes.get(i);
              if (axis != null) {
                  axis.removeChangeListener(this);
              }
          }
          this.domainAxes.clear();
          fireChangeEvent();
      }
  
      /**
       * Configures the domain axes.
       */
      public void configureDomainAxes() {
          for (int i = 0; i < this.domainAxes.size(); i++) {
              ValueAxis axis = this.domainAxes.get(i);
              if (axis != null) {
                  axis.configure();
              }
          }
      }
  
      /**
       * Returns the location for a domain axis.  If this hasn't been set
       * explicitly, the method returns the location that is opposite to the
       * primary domain axis location.
       *
       * @param index  the axis index.
       *
       * @return The location (never <code>null</code>).
       *
       * @see #setDomainAxisLocation(int, AxisLocation)
       */
      public AxisLocation getDomainAxisLocation(int index) {
          AxisLocation result = null;
          if (index < this.domainAxisLocations.size()) {
              result = this.domainAxisLocations.get(index);
          }
          if (result == null) {
              result = AxisLocation.getOpposite(getDomainAxisLocation());
          }
          return result;
      }
  
      /**
       * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param index  the axis index.
       * @param location  the location (<code>null</code> not permitted for index
       *     0).
       *
       * @see #getDomainAxisLocation(int)
       */
      public void setDomainAxisLocation(int index, AxisLocation location) {
          // delegate...
          setDomainAxisLocation(index, location, true);
      }
  
      /**
       * Sets the axis location for a domain axis and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param index  the axis index.
       * @param location  the location (<code>null</code> not permitted for
       *     index 0).
       * @param notify  notify listeners?
       *
       * @since 1.0.5
       *
       * @see #getDomainAxisLocation(int)
       * @see #setRangeAxisLocation(int, AxisLocation, boolean)
       */
      public void setDomainAxisLocation(int index, AxisLocation location,
              boolean notify) {
  
          if (index == 0 && location == null) {
              throw new IllegalArgumentException(
                      "Null 'location' for index 0 not permitted.");
          }
          this.domainAxisLocations.set(index, location);
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the edge for a domain axis.
       *
       * @param index  the axis index.
       *
       * @return The edge.
       *
       * @see #getRangeAxisEdge(int)
       */
      public RectangleEdge getDomainAxisEdge(int index) {
          AxisLocation location = getDomainAxisLocation(index);
          return Plot.resolveDomainAxisLocation(location, this.orientation);
      }
  
      /**
       * Returns the range axis for the plot.  If the range axis for this plot is
       * <code>null</code>, then the method will return the parent plot's range
       * axis (if there is a parent plot).
       *
       * @return The range axis.
       *
       * @see #getRangeAxis(int)
       * @see #setRangeAxis(ValueAxis)
       */
      public ValueAxis getRangeAxis() {
          return getRangeAxis(0);
      }
  
      /**
       * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param axis  the axis (<code>null</code> permitted).
       *
       * @see #getRangeAxis()
       * @see #setRangeAxis(int, ValueAxis)
       */
      public void setRangeAxis(ValueAxis axis)  {
  
          if (axis != null) {
              axis.setPlot(this);
          }
  
          // plot is likely registered as a listener with the existing axis...
          ValueAxis existing = getRangeAxis();
          if (existing != null) {
              existing.removeChangeListener(this);
          }
  
          this.rangeAxes.set(0, axis);
          if (axis != null) {
              axis.configure();
              axis.addChangeListener(this);
          }
          fireChangeEvent();
  
      }
  
      /**
       * Returns the location of the primary range axis.
       *
       * @return The location (never <code>null</code>).
       *
       * @see #setRangeAxisLocation(AxisLocation)
       */
      public AxisLocation getRangeAxisLocation() {
          return this.rangeAxisLocations.get(0);
      }
  
      /**
       * Sets the location of the primary range axis and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param location  the location (<code>null</code> not permitted).
       *
       * @see #getRangeAxisLocation()
       */
      public void setRangeAxisLocation(AxisLocation location) {
          // delegate...
          setRangeAxisLocation(0, location, true);
      }
  
      /**
       * Sets the location of the primary range axis and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param location  the location (<code>null</code> not permitted).
       * @param notify  notify listeners?
       *
       * @see #getRangeAxisLocation()
       */
      public void setRangeAxisLocation(AxisLocation location, boolean notify) {
          // delegate...
          setRangeAxisLocation(0, location, notify);
      }
  
      /**
       * Returns the edge for the primary range axis.
       *
       * @return The range axis edge.
       *
       * @see #getRangeAxisLocation()
       * @see #getOrientation()
       */
      public RectangleEdge getRangeAxisEdge() {
          return Plot.resolveRangeAxisLocation(getRangeAxisLocation(),
                  this.orientation);
      }
  
      /**
       * Returns a range axis.
       *
       * @param index  the axis index.
       *
       * @return The axis (<code>null</code> possible).
       *
       * @see #setRangeAxis(int, ValueAxis)
       */
      public ValueAxis getRangeAxis(int index) {
          ValueAxis result = null;
          if (index < this.rangeAxes.size()) {
              result = this.rangeAxes.get(index);
          }
          if (result == null) {
              Plot parent = getParent();
              if (parent instanceof XYPlot) {
                  XYPlot xy = (XYPlot) parent;
                  result = xy.getRangeAxis(index);
              }
          }
          return result;
      }
  
      /**
       * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
       * listeners.
       *
       * @param index  the axis index.
       * @param axis  the axis (<code>null</code> permitted).
       *
       * @see #getRangeAxis(int)
       */
      public void setRangeAxis(int index, ValueAxis axis) {
          setRangeAxis(index, axis, true);
      }
  
      /**
       * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param index  the axis index.
       * @param axis  the axis (<code>null</code> permitted).
       * @param notify  notify listeners?
       *
       * @see #getRangeAxis(int)
       */
      public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
          ValueAxis existing = getRangeAxis(index);
          if (existing != null) {
              existing.removeChangeListener(this);
          }
          if (axis != null) {
              axis.setPlot(this);
          }
          this.rangeAxes.set(index, axis);
          if (axis != null) {
              axis.configure();
              axis.addChangeListener(this);
          }
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param axes  the axes (<code>null</code> not permitted).
       *
       * @see #setDomainAxes(ValueAxis[])
       */
      public void setRangeAxes(ValueAxis[] axes) {
          for (int i = 0; i < axes.length; i++) {
              setRangeAxis(i, axes[i], false);
          }
          fireChangeEvent();
      }
  
      /**
       * Returns the number of range axes.
       *
       * @return The axis count.
       *
       * @see #getDomainAxisCount()
       */
      public int getRangeAxisCount() {
          return this.rangeAxes.size();
      }
  
      /**
       * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @see #clearDomainAxes()
       */
      public void clearRangeAxes() {
          for (int i = 0; i < this.rangeAxes.size(); i++) {
              ValueAxis axis = this.rangeAxes.get(i);
              if (axis != null) {
                  axis.removeChangeListener(this);
              }
          }
          this.rangeAxes.clear();
          fireChangeEvent();
      }
  
      /**
       * Configures the range axes.
       *
       * @see #configureDomainAxes()
       */
      public void configureRangeAxes() {
          for (int i = 0; i < this.rangeAxes.size(); i++) {
              ValueAxis axis = this.rangeAxes.get(i);
              if (axis != null) {
                  axis.configure();
              }
          }
      }
  
      /**
       * Returns the location for a range axis.  If this hasn't been set
       * explicitly, the method returns the location that is opposite to the
       * primary range axis location.
       *
       * @param index  the axis index.
       *
       * @return The location (never <code>null</code>).
       *
       * @see #setRangeAxisLocation(int, AxisLocation)
       */
      public AxisLocation getRangeAxisLocation(int index) {
          AxisLocation result = null;
          if (index < this.rangeAxisLocations.size()) {
              result = this.rangeAxisLocations.get(index);
          }
          if (result == null) {
              result = AxisLocation.getOpposite(getRangeAxisLocation());
          }
          return result;
      }
  
      /**
       * Sets the location for a range axis and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param index  the axis index.
       * @param location  the location (<code>null</code> permitted).
       *
       * @see #getRangeAxisLocation(int)
       */
      public void setRangeAxisLocation(int index, AxisLocation location) {
          // delegate...
          setRangeAxisLocation(index, location, true);
      }
  
      /**
       * Sets the axis location for a domain axis and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param index  the axis index.
       * @param location  the location (<code>null</code> not permitted for
       *     index 0).
       * @param notify  notify listeners?
       *
       * @since 1.0.5
       *
       * @see #getRangeAxisLocation(int)
       * @see #setDomainAxisLocation(int, AxisLocation, boolean)
       */
      public void setRangeAxisLocation(int index, AxisLocation location,
              boolean notify) {
  
          if (index == 0 && location == null) {
              throw new IllegalArgumentException(
                      "Null 'location' for index 0 not permitted.");
          }
          this.rangeAxisLocations.set(index, location);
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the edge for a range axis.
       *
       * @param index  the axis index.
       *
       * @return The edge.
       *
       * @see #getRangeAxisLocation(int)
       * @see #getOrientation()
       */
      public RectangleEdge getRangeAxisEdge(int index) {
          AxisLocation location = getRangeAxisLocation(index);
          return Plot.resolveRangeAxisLocation(location, this.orientation);
      }
  
      /**
       * Returns the primary dataset for the plot.
       *
       * @return The primary dataset (possibly <code>null</code>).
       *
       * @see #getDataset(int)
       * @see #setDataset(XYDataset)
       */
      public XYDataset getDataset() {
          return getDataset(0);
      }
  
      /**
       * Returns a dataset.
       *
       * @param index  the dataset index.
       *
       * @return The dataset (possibly <code>null</code>).
       *
       * @see #setDataset(int, XYDataset)
       */
      public XYDataset getDataset(int index) {
          XYDataset result = null;
          if (this.datasets.size() > index) {
              result = this.datasets.get(index);
          }
          return result;
      }
  
      /**
       * Sets the primary dataset for the plot, replacing the existing dataset if
       * there is one.
       *
       * @param dataset  the dataset (<code>null</code> permitted).
       *
       * @see #getDataset()
       * @see #setDataset(int, XYDataset)
       */
      public void setDataset(XYDataset dataset) {
          setDataset(0, dataset);
      }
  
      /**
       * Sets a dataset for the plot.
       *
       * @param index  the dataset index.
       * @param dataset  the dataset (<code>null</code> permitted).
       *
       * @see #getDataset(int)
       */
      public void setDataset(int index, XYDataset dataset) {
          XYDataset existing = getDataset(index);
          if (existing != null) {
              existing.removeChangeListener(this);
          }
          this.datasets.set(index, dataset);
          if (dataset != null) {
              dataset.addChangeListener(this);
          }
  
          // send a dataset change event to self...
          DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
          datasetChanged(event);
      }
  
      /**
       * Returns the number of datasets.
       *
       * @return The number of datasets.
       */
      public int getDatasetCount() {
          return this.datasets.size();
      }
  
      /**
       * Returns the index of the specified dataset, or <code>-1</code> if the
       * dataset does not belong to the plot.
       *
       * @param dataset  the dataset (<code>null</code> not permitted).
       *
       * @return The index.
       */
      public int indexOf(XYDataset dataset) {
          int result = -1;
          for (int i = 0; i < this.datasets.size(); i++) {
              if (dataset == this.datasets.get(i)) {
                  result = i;
                  break;
              }
          }
          return result;
      }
  
      /**
       * Maps a dataset to a particular domain axis.  All data will be plotted
       * against axis zero by default, no mapping is required for this case.
       *
       * @param index  the dataset index (zero-based).
       * @param axisIndex  the axis index.
       *
       * @see #mapDatasetToRangeAxis(int, int)
       */
      public void mapDatasetToDomainAxis(int index, int axisIndex) {
          List<Integer> axisIndices = new java.util.ArrayList<Integer>(1);
          axisIndices.add(axisIndex);
          mapDatasetToDomainAxes(index, axisIndices);
      }
  
      /**
       * Maps the specified dataset to the axes in the list.  Note that the
       * conversion of data values into Java2D space is always performed using
       * the first axis in the list.
       *
       * @param index  the dataset index (zero-based).
       * @param axisIndices  the axis indices (<code>null</code> permitted).
       *
       * @since 1.0.12
       */
      public void mapDatasetToDomainAxes(int index, List<Integer> axisIndices) {
          if (index < 0) {
              throw new IllegalArgumentException("Requires 'index' >= 0.");
          }
          checkAxisIndices(axisIndices);
          Integer key = index;
          this.datasetToDomainAxesMap.put(key,
                  new ArrayList<Integer>(axisIndices));
          // fake a dataset change event to update axes...
          datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
      }
  
      /**
       * Maps a dataset to a particular range axis.  All data will be plotted
       * against axis zero by default, no mapping is required for this case.
       *
       * @param index  the dataset index (zero-based).
       * @param axisIndex  the axis index.
       *
       * @see #mapDatasetToDomainAxis(int, int)
       */
      public void mapDatasetToRangeAxis(int index, int axisIndex) {
          List<Integer> axisIndices = new java.util.ArrayList<Integer>(1);
          axisIndices.add(axisIndex);
          mapDatasetToRangeAxes(index, axisIndices);
      }
  
      /**
       * Maps the specified dataset to the axes in the list.  Note that the
       * conversion of data values into Java2D space is always performed using
       * the first axis in the list.
       *
       * @param index  the dataset index (zero-based).
       * @param axisIndices  the axis indices (<code>null</code> permitted).
       *
       * @since 1.0.12
       */
      public void mapDatasetToRangeAxes(int index, List<Integer> axisIndices) {
          if (index < 0) {
              throw new IllegalArgumentException("Requires 'index' >= 0.");
          }
          checkAxisIndices(axisIndices);
          Integer key = index;
          this.datasetToRangeAxesMap.put(key,
                  new ArrayList<Integer>(axisIndices));
          // fake a dataset change event to update axes...
          datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
      }
  
      /**
       * This method is used to perform argument checking on the list of
       * axis indices passed to mapDatasetToDomainAxes() and
       * mapDatasetToRangeAxes().
       *
       * @param indices  the list of indices (<code>null</code> permitted).
       */
      private void checkAxisIndices(List<Integer> indices) {
          // axisIndices can be:
          // 1.  null;
          // 2.  non-empty, containing only Integer objects that are unique.
          if (indices == null) {
              return;  // OK
          }
          int count = indices.size();
          if (count == 0) {
              throw new IllegalArgumentException("Empty list not permitted.");
          }
          Set<Integer> set = new HashSet<Integer>();
          for (Integer item : indices) {
              if (set.contains(item)) {
                  throw new IllegalArgumentException("Indices must be unique.");
              }
              set.add(item);
          }
      }
  
      /**
       * Returns the number of renderer slots for this plot.
       *
       * @return The number of renderer slots.
       *
       * @since 1.0.11
       */
      public int getRendererCount() {
          return this.renderers.size();
      }
  
      /**
       * Returns the renderer for the primary dataset.
       *
       * @return The item renderer (possibly <code>null</code>).
       *
       * @see #setRenderer(XYItemRenderer)
       */
      public XYItemRenderer getRenderer() {
          return getRenderer(0);
      }
  
      /**
       * Returns the renderer for a dataset, or <code>null</code>.
       *
       * @param index  the renderer index.
       *
       * @return The renderer (possibly <code>null</code>).
       *
       * @see #setRenderer(int, XYItemRenderer)
       */
      public XYItemRenderer getRenderer(int index) {
          XYItemRenderer result = null;
          if (this.renderers.size() > index) {
              result = this.renderers.get(index);
          }
          return result;
  
      }
  
      /**
       * Sets the renderer for the primary dataset and sends a
       * {@link PlotChangeEvent} to all registered listeners.  If the renderer
       * is set to <code>null</code>, no data will be displayed.
       *
       * @param renderer  the renderer (<code>null</code> permitted).
       *
       * @see #getRenderer()
       */
      public void setRenderer(XYItemRenderer renderer) {
          setRenderer(0, renderer);
      }
  
      /**
       * Sets a renderer and sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @param index  the index.
       * @param renderer  the renderer.
       *
       * @see #getRenderer(int)
       */
      public void setRenderer(int index, XYItemRenderer renderer) {
          setRenderer(index, renderer, true);
      }
  
      /**
       * Sets a renderer and sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @param index  the index.
       * @param renderer  the renderer.
       * @param notify  notify listeners?
       *
       * @see #getRenderer(int)
       */
      public void setRenderer(int index, XYItemRenderer renderer,
                              boolean notify) {
          XYItemRenderer existing = getRenderer(index);
          if (existing != null) {
              existing.removeChangeListener(this);
          }
          this.renderers.set(index, renderer);
          if (renderer != null) {
              renderer.setPlot(this);
              renderer.addChangeListener(this);
          }
          configureDomainAxes();
          configureRangeAxes();
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Sets the renderers for this plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param renderers  the renderers (<code>null</code> not permitted).
       */
      public void setRenderers(XYItemRenderer[] renderers) {
          for (int i = 0; i < renderers.length; i++) {
              setRenderer(i, renderers[i], false);
          }
          fireChangeEvent();
      }
  
      /**
       * Returns the dataset rendering order.
       *
       * @return The order (never <code>null</code>).
       *
       * @see #setDatasetRenderingOrder(DatasetRenderingOrder)
       */
      public DatasetRenderingOrder getDatasetRenderingOrder() {
          return this.datasetRenderingOrder;
      }
  
      /**
       * Sets the rendering order and sends a {@link PlotChangeEvent} to all
       * registered listeners.  By default, the plot renders the primary dataset
       * last (so that the primary dataset overlays the secondary datasets).
       * You can reverse this if you want to.
       *
       * @param order  the rendering order (<code>null</code> not permitted).
       *
       * @see #getDatasetRenderingOrder()
       */
      public void setDatasetRenderingOrder(DatasetRenderingOrder order) {
          ParamChecks.nullNotPermitted(order, "order");
          this.datasetRenderingOrder = order;
          fireChangeEvent();
      }
  
      /**
       * Returns the series rendering order.
       *
       * @return the order (never <code>null</code>).
       *
       * @see #setSeriesRenderingOrder(SeriesRenderingOrder)
       */
      public SeriesRenderingOrder getSeriesRenderingOrder() {
          return this.seriesRenderingOrder;
      }
  
      /**
       * Sets the series order and sends a {@link PlotChangeEvent} to all
       * registered listeners.  By default, the plot renders the primary series
       * last (so that the primary series appears to be on top).
       * You can reverse this if you want to.
       *
       * @param order  the rendering order (<code>null</code> not permitted).
       *
       * @see #getSeriesRenderingOrder()
       */
      public void setSeriesRenderingOrder(SeriesRenderingOrder order) {
          if (order == null) {
              throw new IllegalArgumentException("Null 'order' argument.");
          }
          this.seriesRenderingOrder = order;
          fireChangeEvent();
      }
  
      /**
       * Returns the index of the specified renderer, or <code>-1</code> if the
       * renderer is not assigned to this plot.
       *
       * @param renderer  the renderer (<code>null</code> permitted).
       *
       * @return The renderer index.
       */
      public int getIndexOf(XYItemRenderer renderer) {
          return this.renderers.indexOf(renderer);
      }
  
      /**
       * Returns the renderer for the specified dataset.  The code first
       * determines the index of the dataset, then checks if there is a
       * renderer with the same index (if not, the method returns renderer(0).
       *
       * @param dataset  the dataset (<code>null</code> permitted).
       *
       * @return The renderer (possibly <code>null</code>).
       */
      public XYItemRenderer getRendererForDataset(XYDataset dataset) {
          XYItemRenderer result = null;
          for (int i = 0; i < this.datasets.size(); i++) {
              if (this.datasets.get(i) == dataset) {
                  result = this.renderers.get(i);
                  if (result == null) {
                      result = getRenderer();
                  }
                  break;
              }
          }
          return result;
      }
  
      /**
       * Returns the weight for this plot when it is used as a subplot within a
       * combined plot.
       *
       * @return The weight.
       *
       * @see #setWeight(int)
       */
      public int getWeight() {
          return this.weight;
      }
  
      /**
       * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @param weight  the weight.
       *
       * @see #getWeight()
       */
      public void setWeight(int weight) {
          this.weight = weight;
          fireChangeEvent();
      }
  
      /**
       * Returns <code>true</code> if the domain gridlines are visible, and
       * <code>false</code> otherwise.
       *
       * @return <code>true</code> or <code>false</code>.
       *
       * @see #setDomainGridlinesVisible(boolean)
       */
      public boolean isDomainGridlinesVisible() {
          return this.domainGridlinesVisible;
      }
  
      /**
       * Sets the flag that controls whether or not the domain grid-lines are
       * visible.
       * <p>
       * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
       * registered listeners.
       *
       * @param visible  the new value of the flag.
       *
       * @see #isDomainGridlinesVisible()
       */
      public void setDomainGridlinesVisible(boolean visible) {
          if (this.domainGridlinesVisible != visible) {
              this.domainGridlinesVisible = visible;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns <code>true</code> if the domain minor gridlines are visible, and
       * <code>false</code> otherwise.
       *
       * @return <code>true</code> or <code>false</code>.
       *
       * @see #setDomainMinorGridlinesVisible(boolean)
       *
       * @since 1.0.12
       */
      public boolean isDomainMinorGridlinesVisible() {
          return this.domainMinorGridlinesVisible;
      }
  
      /**
       * Sets the flag that controls whether or not the domain minor grid-lines
       * are visible.
       * <p>
       * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
       * registered listeners.
       *
       * @param visible  the new value of the flag.
       *
       * @see #isDomainMinorGridlinesVisible()
       *
       * @since 1.0.12
       */
      public void setDomainMinorGridlinesVisible(boolean visible) {
          if (this.domainMinorGridlinesVisible != visible) {
              this.domainMinorGridlinesVisible = visible;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the stroke for the grid-lines (if any) plotted against the
       * domain axis.
       *
       * @return The stroke (never <code>null</code>).
       *
       * @see #setDomainGridlineStroke(Stroke)
       */
      public Stroke getDomainGridlineStroke() {
          return this.domainGridlineStroke;
      }
  
      /**
       * Sets the stroke for the grid lines plotted against the domain axis, and
       * sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param stroke  the stroke (<code>null</code> not permitted).
       *
       * @throws IllegalArgumentException if <code>stroke</code> is
       *     <code>null</code>.
       *
       * @see #getDomainGridlineStroke()
       */
      public void setDomainGridlineStroke(Stroke stroke) {
          ParamChecks.nullNotPermitted(stroke, "stroke");
          this.domainGridlineStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the stroke for the minor grid-lines (if any) plotted against the
       * domain axis.
       *
       * @return The stroke (never <code>null</code>).
       *
       * @see #setDomainMinorGridlineStroke(Stroke)
       *
       * @since 1.0.12
       */
  
      public Stroke getDomainMinorGridlineStroke() {
          return this.domainMinorGridlineStroke;
      }
  
      /**
       * Sets the stroke for the minor grid lines plotted against the domain
       * axis, and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param stroke  the stroke (<code>null</code> not permitted).
       *
       * @throws IllegalArgumentException if <code>stroke</code> is
       *     <code>null</code>.
       *
       * @see #getDomainMinorGridlineStroke()
       *
       * @since 1.0.12
       */
      public void setDomainMinorGridlineStroke(Stroke stroke) {
          if (stroke == null) {
              throw new IllegalArgumentException("Null 'stroke' argument.");
          }
          this.domainMinorGridlineStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint for the grid lines (if any) plotted against the domain
       * axis.
       *
       * @return The paint (never <code>null</code>).
       *
       * @see #setDomainGridlinePaint(Paint)
       */
      public Paint getDomainGridlinePaint() {
          return this.domainGridlinePaint;
      }
  
      /**
       * Sets the paint for the grid lines plotted against the domain axis, and
       * sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint  the paint (<code>null</code> not permitted).
       *
       * @throws IllegalArgumentException if <code>paint</code> is
       *     <code>null</code>.
       *
       * @see #getDomainGridlinePaint()
       */
      public void setDomainGridlinePaint(Paint paint) {
          ParamChecks.nullNotPermitted(paint, "paint");
          this.domainGridlinePaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint for the minor grid lines (if any) plotted against the
       * domain axis.
       *
       * @return The paint (never <code>null</code>).
       *
       * @see #setDomainMinorGridlinePaint(Paint)
       *
       * @since 1.0.12
       */
      public Paint getDomainMinorGridlinePaint() {
          return this.domainMinorGridlinePaint;
      }
  
      /**
       * Sets the paint for the minor grid lines plotted against the domain axis,
       * and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint  the paint (<code>null</code> not permitted).
       *
       * @throws IllegalArgumentException if <code>paint</code> is
       *     <code>null</code>.
       *
       * @see #getDomainMinorGridlinePaint()
       *
       * @since 1.0.12
       */
      public void setDomainMinorGridlinePaint(Paint paint) {
          if (paint == null) {
              throw new IllegalArgumentException("Null 'paint' argument.");
          }
          this.domainMinorGridlinePaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns <code>true</code> if the range axis grid is visible, and
       * <code>false</code> otherwise.
       *
       * @return A boolean.
       *
       * @see #setRangeGridlinesVisible(boolean)
       */
      public boolean isRangeGridlinesVisible() {
          return this.rangeGridlinesVisible;
      }
  
      /**
       * Sets the flag that controls whether or not the range axis grid lines
       * are visible.
       * <p>
       * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
       * registered listeners.
       *
       * @param visible  the new value of the flag.
       *
       * @see #isRangeGridlinesVisible()
       */
      public void setRangeGridlinesVisible(boolean visible) {
          if (this.rangeGridlinesVisible != visible) {
              this.rangeGridlinesVisible = visible;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the stroke for the grid lines (if any) plotted against the
       * range axis.
       *
       * @return The stroke (never <code>null</code>).
       *
       * @see #setRangeGridlineStroke(Stroke)
       */
      public Stroke getRangeGridlineStroke() {
          return this.rangeGridlineStroke;
      }
  
      /**
       * Sets the stroke for the grid lines plotted against the range axis,
       * and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param stroke  the stroke (<code>null</code> not permitted).
       *
       * @see #getRangeGridlineStroke()
       */
      public void setRangeGridlineStroke(Stroke stroke) {
          ParamChecks.nullNotPermitted(stroke, "stroke");
          this.rangeGridlineStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint for the grid lines (if any) plotted against the range
       * axis.
       *
       * @return The paint (never <code>null</code>).
       *
       * @see #setRangeGridlinePaint(Paint)
       */
      public Paint getRangeGridlinePaint() {
          return this.rangeGridlinePaint;
      }
  
      /**
       * Sets the paint for the grid lines plotted against the range axis and
       * sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint  the paint (<code>null</code> not permitted).
       *
       * @see #getRangeGridlinePaint()
       */
      public void setRangeGridlinePaint(Paint paint) {
          if (paint == null) {
              throw new IllegalArgumentException("Null 'paint' argument.");
          }
          this.rangeGridlinePaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns <code>true</code> if the range axis minor grid is visible, and
       * <code>false</code> otherwise.
       *
       * @return A boolean.
       *
       * @see #setRangeMinorGridlinesVisible(boolean)
       *
       * @since 1.0.12
       */
      public boolean isRangeMinorGridlinesVisible() {
          return this.rangeMinorGridlinesVisible;
      }
  
      /**
       * Sets the flag that controls whether or not the range axis minor grid
       * lines are visible.
       * <p>
       * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
       * registered listeners.
       *
       * @param visible  the new value of the flag.
       *
       * @see #isRangeMinorGridlinesVisible()
       *
       * @since 1.0.12
       */
      public void setRangeMinorGridlinesVisible(boolean visible) {
          if (this.rangeMinorGridlinesVisible != visible) {
              this.rangeMinorGridlinesVisible = visible;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the stroke for the minor grid lines (if any) plotted against the
       * range axis.
       *
       * @return The stroke (never <code>null</code>).
       *
       * @see #setRangeMinorGridlineStroke(Stroke)
       *
       * @since 1.0.12
       */
      public Stroke getRangeMinorGridlineStroke() {
          return this.rangeMinorGridlineStroke;
      }
  
      /**
       * Sets the stroke for the minor grid lines plotted against the range axis,
       * and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param stroke  the stroke (<code>null</code> not permitted).
       *
       * @see #getRangeMinorGridlineStroke()
       *
       * @since 1.0.12
       */
      public void setRangeMinorGridlineStroke(Stroke stroke) {
          ParamChecks.nullNotPermitted(stroke, "stroke");
          this.rangeMinorGridlineStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint for the minor grid lines (if any) plotted against the
       * range axis.
       *
       * @return The paint (never <code>null</code>).
       *
       * @see #setRangeMinorGridlinePaint(Paint)
       *
       * @since 1.0.12
       */
      public Paint getRangeMinorGridlinePaint() {
          return this.rangeMinorGridlinePaint;
      }
  
      /**
       * Sets the paint for the minor grid lines plotted against the range axis
       * and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint  the paint (<code>null</code> not permitted).
       *
       * @see #getRangeMinorGridlinePaint()
       *
       * @since 1.0.12
       */
      public void setRangeMinorGridlinePaint(Paint paint) {
          if (paint == null) {
              throw new IllegalArgumentException("Null 'paint' argument.");
          }
          this.rangeMinorGridlinePaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns a flag that controls whether or not a zero baseline is
       * displayed for the domain axis.
       *
       * @return A boolean.
       *
       * @since 1.0.5
       *
       * @see #setDomainZeroBaselineVisible(boolean)
       */
      public boolean isDomainZeroBaselineVisible() {
          return this.domainZeroBaselineVisible;
      }
  
      /**
       * Sets the flag that controls whether or not the zero baseline is
       * displayed for the domain axis, and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param visible  the flag.
       *
       * @since 1.0.5
       *
       * @see #isDomainZeroBaselineVisible()
       */
      public void setDomainZeroBaselineVisible(boolean visible) {
          this.domainZeroBaselineVisible = visible;
          fireChangeEvent();
      }
  
      /**
       * Returns the stroke used for the zero baseline against the domain axis.
       *
       * @return The stroke (never <code>null</code>).
       *
       * @since 1.0.5
       *
       * @see #setDomainZeroBaselineStroke(Stroke)
       */
      public Stroke getDomainZeroBaselineStroke() {
          return this.domainZeroBaselineStroke;
      }
  
      /**
       * Sets the stroke for the zero baseline for the domain axis,
       * and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param stroke  the stroke (<code>null</code> not permitted).
       *
       * @since 1.0.5
       *
       * @see #getRangeZeroBaselineStroke()
       */
      public void setDomainZeroBaselineStroke(Stroke stroke) {
          ParamChecks.nullNotPermitted(stroke, "stroke");
          this.domainZeroBaselineStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint for the zero baseline (if any) plotted against the
       * domain axis.
       *
       * @since 1.0.5
       *
       * @return The paint (never <code>null</code>).
       *
       * @see #setDomainZeroBaselinePaint(Paint)
       */
      public Paint getDomainZeroBaselinePaint() {
          return this.domainZeroBaselinePaint;
      }
  
      /**
       * Sets the paint for the zero baseline plotted against the domain axis and
       * sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint  the paint (<code>null</code> not permitted).
       *
       * @since 1.0.5
       *
       * @see #getDomainZeroBaselinePaint()
       */
      public void setDomainZeroBaselinePaint(Paint paint) {
          if (paint == null) {
              throw new IllegalArgumentException("Null 'paint' argument.");
          }
          this.domainZeroBaselinePaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns a flag that controls whether or not a zero baseline is
       * displayed for the range axis.
       *
       * @return A boolean.
       *
       * @see #setRangeZeroBaselineVisible(boolean)
       */
      public boolean isRangeZeroBaselineVisible() {
          return this.rangeZeroBaselineVisible;
      }
  
      /**
       * Sets the flag that controls whether or not the zero baseline is
       * displayed for the range axis, and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param visible  the flag.
       *
       * @see #isRangeZeroBaselineVisible()
       */
      public void setRangeZeroBaselineVisible(boolean visible) {
          this.rangeZeroBaselineVisible = visible;
          fireChangeEvent();
      }
  
      /**
       * Returns the stroke used for the zero baseline against the range axis.
       *
       * @return The stroke (never <code>null</code>).
       *
       * @see #setRangeZeroBaselineStroke(Stroke)
       */
      public Stroke getRangeZeroBaselineStroke() {
          return this.rangeZeroBaselineStroke;
      }
  
      /**
       * Sets the stroke for the zero baseline for the range axis,
       * and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param stroke  the stroke (<code>null</code> not permitted).
       *
       * @see #getRangeZeroBaselineStroke()
       */
      public void setRangeZeroBaselineStroke(Stroke stroke) {
          ParamChecks.nullNotPermitted(stroke, "stroke");
          this.rangeZeroBaselineStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint for the zero baseline (if any) plotted against the
       * range axis.
       *
       * @return The paint (never <code>null</code>).
       *
       * @see #setRangeZeroBaselinePaint(Paint)
       */
      public Paint getRangeZeroBaselinePaint() {
          return this.rangeZeroBaselinePaint;
      }
  
      /**
       * Sets the paint for the zero baseline plotted against the range axis and
       * sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint  the paint (<code>null</code> not permitted).
       *
       * @see #getRangeZeroBaselinePaint()
       */
      public void setRangeZeroBaselinePaint(Paint paint) {
          if (paint == null) {
              throw new IllegalArgumentException("Null 'paint' argument.");
          }
          this.rangeZeroBaselinePaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint used for the domain tick bands.  If this is
       * <code>null</code>, no tick bands will be drawn.
       *
       * @return The paint (possibly <code>null</code>).
       *
       * @see #setDomainTickBandPaint(Paint)
       */
      public Paint getDomainTickBandPaint() {
          return this.domainTickBandPaint;
      }
  
      /**
       * Sets the paint for the domain tick bands.
       *
       * @param paint  the paint (<code>null</code> permitted).
       *
       * @see #getDomainTickBandPaint()
       */
      public void setDomainTickBandPaint(Paint paint) {
          this.domainTickBandPaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint used for the range tick bands.  If this is
       * <code>null</code>, no tick bands will be drawn.
       *
       * @return The paint (possibly <code>null</code>).
       *
       * @see #setRangeTickBandPaint(Paint)
       */
      public Paint getRangeTickBandPaint() {
          return this.rangeTickBandPaint;
      }
  
      /**
       * Sets the paint for the range tick bands.
       *
       * @param paint  the paint (<code>null</code> permitted).
       *
       * @see #getRangeTickBandPaint()
       */
      public void setRangeTickBandPaint(Paint paint) {
          this.rangeTickBandPaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns the origin for the quadrants that can be displayed on the plot.
       * This defaults to (0, 0).
       *
       * @return The origin point (never <code>null</code>).
       *
       * @see #setQuadrantOrigin(Point2D)
       */
      public Point2D getQuadrantOrigin() {
          return this.quadrantOrigin;
      }
  
      /**
       * Sets the quadrant origin and sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @param origin  the origin (<code>null</code> not permitted).
       *
       * @see #getQuadrantOrigin()
       */
      public void setQuadrantOrigin(Point2D origin) {
          ParamChecks.nullNotPermitted(origin, "origin");
          this.quadrantOrigin = origin;
          fireChangeEvent();
      }
  
      /**
       * Returns the paint used for the specified quadrant.
       *
       * @param index  the quadrant index (0-3).
       *
       * @return The paint (possibly <code>null</code>).
       *
       * @see #setQuadrantPaint(int, Paint)
       */
      public Paint getQuadrantPaint(int index) {
          if (index < 0 || index > 3) {
              throw new IllegalArgumentException("The index value (" + index
                      + ") should be in the range 0 to 3.");
          }
          return this.quadrantPaint[index];
      }
  
      /**
       * Sets the paint used for the specified quadrant and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param index  the quadrant index (0-3).
       * @param paint  the paint (<code>null</code> permitted).
       *
       * @see #getQuadrantPaint(int)
       */
      public void setQuadrantPaint(int index, Paint paint) {
          if (index < 0 || index > 3) {
              throw new IllegalArgumentException("The index value (" + index
                      + ") should be in the range 0 to 3.");
          }
          this.quadrantPaint[index] = paint;
          fireChangeEvent();
      }
  
      /**
       * Adds a marker for the domain axis and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the domain axis, however this is entirely up to the renderer.
       *
       * @param marker  the marker (<code>null</code> not permitted).
       *
       * @see #addDomainMarker(Marker, Layer)
       * @see #clearDomainMarkers()
       */
      public void addDomainMarker(Marker marker) {
          // defer argument checking...
          addDomainMarker(marker, Layer.FOREGROUND);
      }
  
      /**
       * Adds a marker for the domain axis in the specified layer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the domain axis, however this is entirely up to the renderer.
       *
       * @param marker  the marker (<code>null</code> not permitted).
       * @param layer  the layer (foreground or background).
       *
       * @see #addDomainMarker(int, Marker, Layer)
       */
      public void addDomainMarker(Marker marker, Layer layer) {
          addDomainMarker(0, marker, layer);
      }
  
      /**
       * Clears all the (foreground and background) domain markers and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @see #addDomainMarker(int, Marker, Layer)
       */
      public void clearDomainMarkers() {
          if (this.backgroundDomainMarkers != null) {
              Set<Integer> keys = this.backgroundDomainMarkers.keySet();
              for (Integer key : keys) {
                  clearDomainMarkers(key);
              }
              this.backgroundDomainMarkers.clear();
          }
          if (this.foregroundDomainMarkers != null) {
              Set<Integer> keys = this.foregroundDomainMarkers.keySet();
              for (Integer key : keys) {
                  clearDomainMarkers(key);
              }
              this.foregroundDomainMarkers.clear();
          }
          fireChangeEvent();
      }
  
      /**
       * Clears the (foreground and background) domain markers for a particular
       * renderer and sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param key  the renderer index.
       *
       * @see #clearRangeMarkers(int)
       */
      public void clearDomainMarkers(int key) {
          if (this.backgroundDomainMarkers != null) {
              Collection<Marker> markers = this.backgroundDomainMarkers.get(key);
              if (markers != null) {
                  for (Marker m : markers) {
                      m.removeChangeListener(this);
                  }
                  markers.clear();
              }
          }
          if (this.foregroundRangeMarkers != null) {
              Collection<Marker> markers = this.foregroundDomainMarkers.get(key);
              if (markers != null) {
                  for (Marker m : markers) {
                      m.removeChangeListener(this);
                  }
                  markers.clear();
              }
          }
          fireChangeEvent();
      }
  
      /**
       * Adds a marker for a specific dataset/renderer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the domain axis (that the renderer is mapped to), however this is
       * entirely up to the renderer.
       *
       * @param index  the dataset/renderer index.
       * @param marker  the marker.
       * @param layer  the layer (foreground or background).
       *
       * @see #clearDomainMarkers(int)
       * @see #addRangeMarker(int, Marker, Layer)
       */
      public void addDomainMarker(int index, Marker marker, Layer layer) {
          addDomainMarker(index, marker, layer, true);
      }
  
      /**
       * Adds a marker for a specific dataset/renderer and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the domain axis (that the renderer is mapped to), however this is
       * entirely up to the renderer.
       *
       * @param index  the dataset/renderer index.
       * @param marker  the marker.
       * @param layer  the layer (foreground or background).
       * @param notify  notify listeners?
       *
       * @since 1.0.10
       */
      public void addDomainMarker(int index, Marker marker, Layer layer,
              boolean notify) {
          ParamChecks.nullNotPermitted(marker, "marker");
          ParamChecks.nullNotPermitted(layer, "layer");
          Collection<Marker> markers;
          if (layer == Layer.FOREGROUND) {
              markers = this.foregroundDomainMarkers.get(
                      index);
              if (markers == null) {
                  markers = new java.util.ArrayList<Marker>();
                  this.foregroundDomainMarkers.put(index, markers);
              }
              markers.add(marker);
          }
          else if (layer == Layer.BACKGROUND) {
              markers = this.backgroundDomainMarkers.get(index);
              if (markers == null) {
                  markers = new java.util.ArrayList<Marker>();
                  this.backgroundDomainMarkers.put(index, markers);
              }
              markers.add(marker);
          }
          marker.addChangeListener(this);
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Removes a marker for the domain axis and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param marker  the marker.
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.7
       */
      public boolean removeDomainMarker(Marker marker) {
          return removeDomainMarker(marker, Layer.FOREGROUND);
      }
  
      /**
       * Removes a marker for the domain axis in the specified layer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param marker the marker (<code>null</code> not permitted).
       * @param layer the layer (foreground or background).
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.7
       */
      public boolean removeDomainMarker(Marker marker, Layer layer) {
          return removeDomainMarker(0, marker, layer);
      }
  
      /**
       * Removes a marker for a specific dataset/renderer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param index the dataset/renderer index.
       * @param marker the marker.
       * @param layer the layer (foreground or background).
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.7
       */
      public boolean removeDomainMarker(int index, Marker marker, Layer layer) {
          return removeDomainMarker(index, marker, layer, true);
      }
  
      /**
       * Removes a marker for a specific dataset/renderer and, if requested,
       * sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param index  the dataset/renderer index.
       * @param marker  the marker.
       * @param layer  the layer (foreground or background).
       * @param notify  notify listeners?
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.10
       */
      public boolean removeDomainMarker(int index, Marker marker, Layer layer,
              boolean notify) {
          ArrayList markers;
          if (layer == Layer.FOREGROUND) {
              markers = (ArrayList) this.foregroundDomainMarkers.get(
                      index);
          }
          else {
              markers = (ArrayList) this.backgroundDomainMarkers.get(
                      index);
          }
          if (markers == null) {
              return false;
          }
          boolean removed = markers.remove(marker);
          if (removed && notify) {
              fireChangeEvent();
          }
          return removed;
      }
  
      /**
       * Adds a marker for the range axis and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the range axis, however this is entirely up to the renderer.
       *
       * @param marker  the marker (<code>null</code> not permitted).
       *
       * @see #addRangeMarker(Marker, Layer)
       */
      public void addRangeMarker(Marker marker) {
          addRangeMarker(marker, Layer.FOREGROUND);
      }
  
      /**
       * Adds a marker for the range axis in the specified layer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the range axis, however this is entirely up to the renderer.
       *
       * @param marker  the marker (<code>null</code> not permitted).
       * @param layer  the layer (foreground or background).
       *
       * @see #addRangeMarker(int, Marker, Layer)
       */
      public void addRangeMarker(Marker marker, Layer layer) {
          addRangeMarker(0, marker, layer);
      }
  
      /**
       * Clears all the range markers and sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @see #clearRangeMarkers()
       */
      public void clearRangeMarkers() {
          if (this.backgroundRangeMarkers != null) {
              Set<Integer> keys = this.backgroundRangeMarkers.keySet();
              for (Integer key : keys) {
                  clearRangeMarkers(key);
              }
              this.backgroundRangeMarkers.clear();
          }
          if (this.foregroundRangeMarkers != null) {
              Set<Integer> keys = this.foregroundRangeMarkers.keySet();
              for (Integer key : keys) {
                  clearRangeMarkers(key);
              }
              this.foregroundRangeMarkers.clear();
          }
          fireChangeEvent();
      }
  
      /**
       * Adds a marker for a specific dataset/renderer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the range axis, however this is entirely up to the renderer.
       *
       * @param index  the dataset/renderer index.
       * @param marker  the marker.
       * @param layer  the layer (foreground or background).
       *
       * @see #clearRangeMarkers(int)
       * @see #addDomainMarker(int, Marker, Layer)
       */
      public void addRangeMarker(int index, Marker marker, Layer layer) {
          addRangeMarker(index, marker, layer, true);
      }
  
      /**
       * Adds a marker for a specific dataset/renderer and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       * <P>
       * Typically a marker will be drawn by the renderer as a line perpendicular
       * to the range axis, however this is entirely up to the renderer.
       *
       * @param index  the dataset/renderer index.
       * @param marker  the marker.
       * @param layer  the layer (foreground or background).
       * @param notify  notify listeners?
       *
       * @since 1.0.10
       */
      public void addRangeMarker(int index, Marker marker, Layer layer,
              boolean notify) {
          Collection<Marker> markers;
          if (layer == Layer.FOREGROUND) {
              markers = this.foregroundRangeMarkers.get(
                      index);
              if (markers == null) {
                  markers = new java.util.ArrayList<Marker>();
                  this.foregroundRangeMarkers.put(index, markers);
              }
              markers.add(marker);
          }
          else if (layer == Layer.BACKGROUND) {
              markers = this.backgroundRangeMarkers.get(
                      index);
              if (markers == null) {
                  markers = new java.util.ArrayList<Marker>();
                  this.backgroundRangeMarkers.put(index, markers);
              }
              markers.add(marker);
          }
          marker.addChangeListener(this);
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Clears the (foreground and background) range markers for a particular
       * renderer.
       *
       * @param key  the renderer index.
       */
      public void clearRangeMarkers(int key) {
          if (this.backgroundRangeMarkers != null) {
              Collection<Marker> markers = this.backgroundRangeMarkers.get(key);
              if (markers != null) {
                  for (Marker m : markers) {
                      m.removeChangeListener(this);
                  }
                  markers.clear();
              }
          }
          if (this.foregroundRangeMarkers != null) {
              Collection<Marker> markers =  this.foregroundRangeMarkers.get(key);
              if (markers != null) {
                  for (Marker m : markers) {
                      m.removeChangeListener(this);
                  }
                  markers.clear();
              }
          }
          fireChangeEvent();
      }
  
      /**
       * Removes a marker for the range axis and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param marker the marker.
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.7
       */
      public boolean removeRangeMarker(Marker marker) {
          return removeRangeMarker(marker, Layer.FOREGROUND);
      }
  
      /**
       * Removes a marker for the range axis in the specified layer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param marker the marker (<code>null</code> not permitted).
       * @param layer the layer (foreground or background).
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.7
       */
      public boolean removeRangeMarker(Marker marker, Layer layer) {
          return removeRangeMarker(0, marker, layer);
      }
  
      /**
       * Removes a marker for a specific dataset/renderer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param index the dataset/renderer index.
       * @param marker the marker (<code>null</code> not permitted).
       * @param layer the layer (foreground or background).
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.7
       */
      public boolean removeRangeMarker(int index, Marker marker, Layer layer) {
          return removeRangeMarker(index, marker, layer, true);
      }
  
      /**
       * Removes a marker for a specific dataset/renderer and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param index  the dataset/renderer index.
       * @param marker  the marker (<code>null</code> not permitted).
       * @param layer  the layer (foreground or background) (<code>null</code> not permitted).
       * @param notify  notify listeners?
       *
       * @return A boolean indicating whether or not the marker was actually
       *         removed.
       *
       * @since 1.0.10
       */
      public boolean removeRangeMarker(int index, Marker marker, Layer layer,
              boolean notify) {
          ParamChecks.nullNotPermitted(marker, "marker");
          ParamChecks.nullNotPermitted(layer, "layer");
          List markers;
          if (layer == Layer.FOREGROUND) {
              markers = (List) this.foregroundRangeMarkers.get(
                      index);
          }
          else {
              markers = (List) this.backgroundRangeMarkers.get(
                      index);
          }
          if (markers == null) {
              return false;
          }
          boolean removed = markers.remove(marker);
          if (removed && notify) {
              fireChangeEvent();
          }
          return removed;
      }
  
      /**
       * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param annotation  the annotation (<code>null</code> not permitted).
       *
       * @see #getAnnotations()
       * @see #removeAnnotation(XYAnnotation)
       */
      public void addAnnotation(XYAnnotation annotation) {
          addAnnotation(annotation, true);
      }
  
      /**
       * Adds an annotation to the plot and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param annotation  the annotation (<code>null</code> not permitted).
       * @param notify  notify listeners?
       *
       * @since 1.0.10
       */
      public void addAnnotation(XYAnnotation annotation, boolean notify) {
          ParamChecks.nullNotPermitted(annotation, "annotation");
          this.annotations.add(annotation);
          annotation.addChangeListener(this);
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param annotation  the annotation (<code>null</code> not permitted).
       *
       * @return A boolean (indicates whether or not the annotation was removed).
       *
       * @see #addAnnotation(XYAnnotation)
       * @see #getAnnotations()
       */
      public boolean removeAnnotation(XYAnnotation annotation) {
          return removeAnnotation(annotation, true);
      }
  
      /**
       * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param annotation  the annotation (<code>null</code> not permitted).
       * @param notify  notify listeners?
       *
       * @return A boolean (indicates whether or not the annotation was removed).
       *
       * @since 1.0.10
       */
      public boolean removeAnnotation(XYAnnotation annotation, boolean notify) {
          ParamChecks.nullNotPermitted(annotation, "annotation");
          boolean removed = this.annotations.remove(annotation);
          annotation.removeChangeListener(this);
          if (removed && notify) {
              fireChangeEvent();
          }
          return removed;
      }
  
      /**
       * Returns the list of annotations.
       *
       * @return The list of annotations.
       *
       * @since 1.0.1
       *
       * @see #addAnnotation(XYAnnotation)
       */
      public List<XYAnnotation> getAnnotations() {
          return new ArrayList<XYAnnotation>(this.annotations);
      }
  
      /**
       * Clears all the annotations and sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @see #addAnnotation(XYAnnotation)
       */
      public void clearAnnotations() {
          for (XYAnnotation annotation : this.annotations) {
              annotation.removeChangeListener(this);
          }
          this.annotations.clear();
          fireChangeEvent();
      }
  
      /**
       * Returns the shadow generator for the plot, if any.
       *
       * @return The shadow generator (possibly <code>null</code>).
       *
       * @since 1.0.14
       */
      public ShadowGenerator getShadowGenerator() {
          return this.shadowGenerator;
      }
  
      /**
       * Sets the shadow generator for the plot and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param generator  the generator (<code>null</code> permitted).
       *
       * @since 1.0.14
       */
      public void setShadowGenerator(ShadowGenerator generator) {
          this.shadowGenerator = generator;
          fireChangeEvent();
      }
  
      /**
       * Calculates the space required for all the axes in the plot.
       *
       * @param g2  the graphics device.
       * @param plotArea  the plot area.
       *
       * @return The required space.
       */
      protected AxisSpace calculateAxisSpace(Graphics2D g2,
                                             Rectangle2D plotArea) {
          AxisSpace space = new AxisSpace();
          space = calculateRangeAxisSpace(g2, plotArea, space);
          Rectangle2D revPlotArea = space.shrink(plotArea, null);
          space = calculateDomainAxisSpace(g2, revPlotArea, space);
          return space;
      }
  
      /**
       * Calculates the space required for the domain axis/axes.
       *
       * @param g2  the graphics device.
       * @param plotArea  the plot area.
       * @param space  a carrier for the result (<code>null</code> permitted).
       *
       * @return The required space.
       */
      protected AxisSpace calculateDomainAxisSpace(Graphics2D g2,
                                                   Rectangle2D plotArea,
                                                   AxisSpace space) {
  
          if (space == null) {
              space = new AxisSpace();
          }
  
          // reserve some space for the domain axis...
          if (this.fixedDomainAxisSpace != null) {
              if (this.orientation == PlotOrientation.HORIZONTAL) {
                  space.ensureAtLeast(this.fixedDomainAxisSpace.getLeft(),
                          RectangleEdge.LEFT);
                  space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(),
                          RectangleEdge.RIGHT);
              }
              else if (this.orientation == PlotOrientation.VERTICAL) {
                  space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(),
                          RectangleEdge.TOP);
                  space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(),
                          RectangleEdge.BOTTOM);
              }
          }
          else {
              // reserve space for the domain axes...
              for (int i = 0; i < this.domainAxes.size(); i++) {
                  Axis axis = this.domainAxes.get(i);
                  if (axis != null) {
                      RectangleEdge edge = getDomainAxisEdge(i);
                      space = axis.reserveSpace(g2, this, plotArea, edge, space);
                  }
              }
          }
  
          return space;
  
      }
  
      /**
       * Calculates the space required for the range axis/axes.
       *
       * @param g2  the graphics device.
       * @param plotArea  the plot area.
       * @param space  a carrier for the result (<code>null</code> permitted).
       *
       * @return The required space.
       */
      protected AxisSpace calculateRangeAxisSpace(Graphics2D g2,
                                                  Rectangle2D plotArea,
                                                  AxisSpace space) {
  
          if (space == null) {
              space = new AxisSpace();
          }
  
          // reserve some space for the range axis...
          if (this.fixedRangeAxisSpace != null) {
              if (this.orientation == PlotOrientation.HORIZONTAL) {
                  space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(),
                          RectangleEdge.TOP);
                  space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(),
                          RectangleEdge.BOTTOM);
              }
              else if (this.orientation == PlotOrientation.VERTICAL) {
                  space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(),
                          RectangleEdge.LEFT);
                  space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(),
                          RectangleEdge.RIGHT);
              }
          }
          else {
              // reserve space for the range axes...
              for (int i = 0; i < this.rangeAxes.size(); i++) {
                  Axis axis = this.rangeAxes.get(i);
                  if (axis != null) {
                      RectangleEdge edge = getRangeAxisEdge(i);
                      space = axis.reserveSpace(g2, this, plotArea, edge, space);
                  }
              }
          }
          return space;
  
      }
  
      /**
       * Trims a rectangle to integer coordinates.
       *
       * @param rect  the incoming rectangle.
       *
       * @return A rectangle with integer coordinates.
       */
      private Rectangle integerise(Rectangle2D rect) {
          int x0 = (int) Math.ceil(rect.getMinX());
          int y0 = (int) Math.ceil(rect.getMinY());
          int x1 = (int) Math.floor(rect.getMaxX());
          int y1 = (int) Math.floor(rect.getMaxY());
          return new Rectangle(x0, y0, (x1 - x0), (y1 - y0));
      }
  
      /**
       * Draws the plot within the specified area on a graphics device.
       *
       * @param g2  the graphics device.
       * @param area  the plot area (in Java2D space).
       * @param anchor  an anchor point in Java2D space (<code>null</code>
       *                permitted).
       * @param parentState  the state from the parent plot, if there is one
       *                     (<code>null</code> permitted).
       * @param info  collects chart drawing information (<code>null</code>
       *              permitted).
       */
      @Override
      public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
              PlotState parentState, PlotRenderingInfo info) {
  
          // if the plot area is too small, just return...
          boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
          boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
          if (b1 || b2) {
              return;
          }
  
          // record the plot area...
          if (info != null) {
              info.setPlotArea(area);
          }
  
          // adjust the drawing area for the plot insets (if any)...
          RectangleInsets insets = getInsets();
          insets.trim(area);
  
          AxisSpace space = calculateAxisSpace(g2, area);
          Rectangle2D dataArea = space.shrink(area, null);
          this.axisOffset.trim(dataArea);
  
          dataArea = integerise(dataArea);
          if (dataArea.isEmpty()) {
              return;
          }
          createAndAddEntity((Rectangle2D) dataArea.clone(), info, null, null);
          if (info != null) {
              info.setDataArea(dataArea);
          }
  
          // draw the plot background and axes...
          drawBackground(g2, dataArea);
          Map<Axis, AxisState> axisStateMap = drawAxes(g2, area, dataArea, info);
  
          PlotOrientation orient = getOrientation();
  
          // the anchor point is typically the point where the mouse last
          // clicked - the crosshairs will be driven off this point...
          if (anchor != null && !dataArea.contains(anchor)) {
              anchor = null;
          }
          CrosshairState crosshairState = new CrosshairState();
          crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
          crosshairState.setAnchor(anchor);
  
          crosshairState.setAnchorX(Double.NaN);
          crosshairState.setAnchorY(Double.NaN);
          if (anchor != null) {
              ValueAxis domainAxis = getDomainAxis();
              if (domainAxis != null) {
                  double x;
                  if (orient == PlotOrientation.VERTICAL) {
                      x = domainAxis.java2DToValue(anchor.getX(), dataArea,
                              getDomainAxisEdge());
                  }
                  else {
                      x = domainAxis.java2DToValue(anchor.getY(), dataArea,
                              getDomainAxisEdge());
                  }
                  crosshairState.setAnchorX(x);
              }
              ValueAxis rangeAxis = getRangeAxis();
              if (rangeAxis != null) {
                  double y;
                  if (orient == PlotOrientation.VERTICAL) {
                      y = rangeAxis.java2DToValue(anchor.getY(), dataArea,
                              getRangeAxisEdge());
                  }
                  else {
                      y = rangeAxis.java2DToValue(anchor.getX(), dataArea,
                              getRangeAxisEdge());
                  }
                  crosshairState.setAnchorY(y);
              }
          }
          crosshairState.setCrosshairX(getDomainCrosshairValue());
          crosshairState.setCrosshairY(getRangeCrosshairValue());
          Shape originalClip = g2.getClip();
          Composite originalComposite = g2.getComposite();
  
          g2.clip(dataArea);
          g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                  getForegroundAlpha()));
  
          AxisState domainAxisState = axisStateMap.get(
                  getDomainAxis());
          if (domainAxisState == null) {
              if (parentState != null) {
                  domainAxisState = parentState.getSharedAxisStates()
                          .get(getDomainAxis());
              }
          }
  
          AxisState rangeAxisState = axisStateMap.get(getRangeAxis());
          if (rangeAxisState == null) {
              if (parentState != null) {
                  rangeAxisState = parentState.getSharedAxisStates()
                          .get(getRangeAxis());
              }
          }
          if (domainAxisState != null) {
              drawDomainTickBands(g2, dataArea, domainAxisState.getTicks());
          }
          if (rangeAxisState != null) {
              drawRangeTickBands(g2, dataArea, rangeAxisState.getTicks());
          }
          if (domainAxisState != null) {
              drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
              drawZeroDomainBaseline(g2, dataArea);
          }
          if (rangeAxisState != null) {
              drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
              drawZeroRangeBaseline(g2, dataArea);
          }
  
          Graphics2D savedG2 = g2;
          BufferedImage dataImage = null;
          if (this.shadowGenerator != null) {
              dataImage = new BufferedImage((int) dataArea.getWidth(),
                      (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB);
              g2 = dataImage.createGraphics();
              g2.translate(-dataArea.getX(), -dataArea.getY());
              g2.setRenderingHints(savedG2.getRenderingHints());
          }
  
          // draw the markers that are associated with a specific renderer...
          for (int i = 0; i < this.renderers.size(); i++) {
              drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND);
          }
          for (int i = 0; i < this.renderers.size(); i++) {
              drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND);
          }
  
          // now draw annotations and render data items...
          boolean foundData = false;
          DatasetRenderingOrder order = getDatasetRenderingOrder();
          if (order == DatasetRenderingOrder.FORWARD) {
  
              // draw background annotations
              int rendererCount = this.renderers.size();
              for (int i = 0; i < rendererCount; i++) {
                  XYItemRenderer r = getRenderer(i);
                  if (r != null) {
                      ValueAxis domainAxis = getDomainAxisForDataset(i);
                      ValueAxis rangeAxis = getRangeAxisForDataset(i);
                      r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
                              Layer.BACKGROUND, info);
                  }
              }
  
              // render data items...
              for (int i = 0; i < getDatasetCount(); i++) {
                  foundData = render(g2, dataArea, i, info, crosshairState)
                      || foundData;
              }
  
              // draw foreground annotations
              for (int i = 0; i < rendererCount; i++) {
                  XYItemRenderer r = getRenderer(i);
                  if (r != null) {
                      ValueAxis domainAxis = getDomainAxisForDataset(i);
                      ValueAxis rangeAxis = getRangeAxisForDataset(i);
                      r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
                              Layer.FOREGROUND, info);
                  }
              }
  
          }
          else if (order == DatasetRenderingOrder.REVERSE) {
  
              // draw background annotations
              int rendererCount = this.renderers.size();
              for (int i = rendererCount - 1; i >= 0; i--) {
                  XYItemRenderer r = getRenderer(i);
                  if (i >= getDatasetCount()) { // we need the dataset to make
                      continue;                 // a link to the axes
                  }
                  if (r != null) {
                      ValueAxis domainAxis = getDomainAxisForDataset(i);
                      ValueAxis rangeAxis = getRangeAxisForDataset(i);
                      r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
                              Layer.BACKGROUND, info);
                  }
              }
  
              for (int i = getDatasetCount() - 1; i >= 0; i--) {
                  foundData = render(g2, dataArea, i, info, crosshairState)
                      || foundData;
              }
  
              // draw foreground annotations
              for (int i = rendererCount - 1; i >= 0; i--) {
                  XYItemRenderer r = getRenderer(i);
                  if (i >= getDatasetCount()) { // we need the dataset to make
                      continue;                 // a link to the axes
                  }
                  if (r != null) {
                      ValueAxis domainAxis = getDomainAxisForDataset(i);
                      ValueAxis rangeAxis = getRangeAxisForDataset(i);
                      r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
                              Layer.FOREGROUND, info);
                  }
              }
  
          }
  
          // draw domain crosshair if required...
          int datasetIndex = crosshairState.getDatasetIndex();
          ValueAxis xAxis = this.getDomainAxisForDataset(datasetIndex);
          RectangleEdge xAxisEdge = getDomainAxisEdge(getDomainAxisIndex(xAxis));
          if (!this.domainCrosshairLockedOnData && anchor != null) {
              double xx;
              if (orient == PlotOrientation.VERTICAL) {
                  xx = xAxis.java2DToValue(anchor.getX(), dataArea, xAxisEdge);
              }
              else {
                  xx = xAxis.java2DToValue(anchor.getY(), dataArea, xAxisEdge);
              }
              crosshairState.setCrosshairX(xx);
          }
          setDomainCrosshairValue(crosshairState.getCrosshairX(), false);
          if (isDomainCrosshairVisible()) {
              double x = getDomainCrosshairValue();
              Paint paint = getDomainCrosshairPaint();
              Stroke stroke = getDomainCrosshairStroke();
              drawDomainCrosshair(g2, dataArea, orient, x, xAxis, stroke, paint);
          }
  
          // draw range crosshair if required...
          ValueAxis yAxis = getRangeAxisForDataset(datasetIndex);
          RectangleEdge yAxisEdge = getRangeAxisEdge(getRangeAxisIndex(yAxis));
          if (!this.rangeCrosshairLockedOnData && anchor != null) {
              double yy;
              if (orient == PlotOrientation.VERTICAL) {
                  yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge);
              } else {
                  yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge);
              }
              crosshairState.setCrosshairY(yy);
          }
          setRangeCrosshairValue(crosshairState.getCrosshairY(), false);
          if (isRangeCrosshairVisible()) {
              double y = getRangeCrosshairValue();
              Paint paint = getRangeCrosshairPaint();
              Stroke stroke = getRangeCrosshairStroke();
              drawRangeCrosshair(g2, dataArea, orient, y, yAxis, stroke, paint);
          }
  
          if (!foundData) {
              drawNoDataMessage(g2, dataArea);
          }
  
          for (int i = 0; i < this.renderers.size(); i++) {
              drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND);
          }
          for (int i = 0; i < this.renderers.size(); i++) {
              drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND);
          }
  
          drawAnnotations(g2, dataArea, info);
          if (this.shadowGenerator != null) {
              BufferedImage shadowImage
                      = this.shadowGenerator.createDropShadow(dataImage);
              g2 = savedG2;
              g2.drawImage(shadowImage, (int) dataArea.getX()
                      + this.shadowGenerator.calculateOffsetX(),
                      (int) dataArea.getY()
                      + this.shadowGenerator.calculateOffsetY(), null);
              g2.drawImage(dataImage, (int) dataArea.getX(),
                      (int) dataArea.getY(), null);
          }
          g2.setClip(originalClip);
          g2.setComposite(originalComposite);
  
          drawOutline(g2, dataArea);
  
      }
  
      /**
       * Draws the background for the plot.
       *
       * @param g2  the graphics device.
       * @param area  the area.
       */
      @Override
      public void drawBackground(Graphics2D g2, Rectangle2D area) {
          fillBackground(g2, area, this.orientation);
          drawQuadrants(g2, area);
          drawBackgroundImage(g2, area);
      }
  
      /**
       * Draws the quadrants.
       *
       * @param g2  the graphics device.
       * @param area  the area.
       *
       * @see #setQuadrantOrigin(Point2D)
       * @see #setQuadrantPaint(int, Paint)
       */
      protected void drawQuadrants(Graphics2D g2, Rectangle2D area) {
          //  0 | 1
          //  --+--
          //  2 | 3
          boolean somethingToDraw = false;
  
          ValueAxis xAxis = getDomainAxis();
          if (xAxis == null) {  // we can't draw quadrants without a valid x-axis
              return;
          }
          double x = xAxis.getRange().constrain(this.quadrantOrigin.getX());
          double xx = xAxis.valueToJava2D(x, area, getDomainAxisEdge());
  
          ValueAxis yAxis = getRangeAxis();
          if (yAxis == null) {  // we can't draw quadrants without a valid y-axis
              return;
          }
          double y = yAxis.getRange().constrain(this.quadrantOrigin.getY());
          double yy = yAxis.valueToJava2D(y, area, getRangeAxisEdge());
  
          double xmin = xAxis.getLowerBound();
          double xxmin = xAxis.valueToJava2D(xmin, area, getDomainAxisEdge());
  
          double xmax = xAxis.getUpperBound();
          double xxmax = xAxis.valueToJava2D(xmax, area, getDomainAxisEdge());
  
          double ymin = yAxis.getLowerBound();
          double yymin = yAxis.valueToJava2D(ymin, area, getRangeAxisEdge());
  
          double ymax = yAxis.getUpperBound();
          double yymax = yAxis.valueToJava2D(ymax, area, getRangeAxisEdge());
  
          Rectangle2D[] r = new Rectangle2D[] {null, null, null, null};
          if (this.quadrantPaint[0] != null) {
              if (x > xmin && y < ymax) {
                  if (this.orientation == PlotOrientation.HORIZONTAL) {
                      r[0] = new Rectangle2D.Double(Math.min(yymax, yy),
                              Math.min(xxmin, xx), Math.abs(yy - yymax),
                              Math.abs(xx - xxmin));
                  }
                  else {  // PlotOrientation.VERTICAL
                      r[0] = new Rectangle2D.Double(Math.min(xxmin, xx),
                              Math.min(yymax, yy), Math.abs(xx - xxmin),
                              Math.abs(yy - yymax));
                  }
                  somethingToDraw = true;
              }
          }
          if (this.quadrantPaint[1] != null) {
              if (x < xmax && y < ymax) {
                  if (this.orientation == PlotOrientation.HORIZONTAL) {
                      r[1] = new Rectangle2D.Double(Math.min(yymax, yy),
                              Math.min(xxmax, xx), Math.abs(yy - yymax),
                              Math.abs(xx - xxmax));
                  }
                  else {  // PlotOrientation.VERTICAL
                      r[1] = new Rectangle2D.Double(Math.min(xx, xxmax),
                              Math.min(yymax, yy), Math.abs(xx - xxmax),
                              Math.abs(yy - yymax));
                  }
                  somethingToDraw = true;
              }
          }
          if (this.quadrantPaint[2] != null) {
              if (x > xmin && y > ymin) {
                  if (this.orientation == PlotOrientation.HORIZONTAL) {
                      r[2] = new Rectangle2D.Double(Math.min(yymin, yy),
                              Math.min(xxmin, xx), Math.abs(yy - yymin),
                              Math.abs(xx - xxmin));
                  }
                  else {  // PlotOrientation.VERTICAL
                      r[2] = new Rectangle2D.Double(Math.min(xxmin, xx),
                              Math.min(yymin, yy), Math.abs(xx - xxmin),
                              Math.abs(yy - yymin));
                  }
                  somethingToDraw = true;
              }
          }
          if (this.quadrantPaint[3] != null) {
              if (x < xmax && y > ymin) {
                  if (this.orientation == PlotOrientation.HORIZONTAL) {
                      r[3] = new Rectangle2D.Double(Math.min(yymin, yy),
                              Math.min(xxmax, xx), Math.abs(yy - yymin),
                              Math.abs(xx - xxmax));
                  }
                  else {  // PlotOrientation.VERTICAL
                      r[3] = new Rectangle2D.Double(Math.min(xx, xxmax),
                              Math.min(yymin, yy), Math.abs(xx - xxmax),
                              Math.abs(yy - yymin));
                  }
                  somethingToDraw = true;
              }
          }
          if (somethingToDraw) {
              Composite originalComposite = g2.getComposite();
              g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
                      getBackgroundAlpha()));
              for (int i = 0; i < 4; i++) {
                  if (this.quadrantPaint[i] != null && r[i] != null) {
                      g2.setPaint(this.quadrantPaint[i]);
                      g2.fill(r[i]);
                  }
              }
              g2.setComposite(originalComposite);
          }
      }
  
      /**
       * Draws the domain tick bands, if any.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param ticks  the ticks.
       *
       * @see #setDomainTickBandPaint(Paint)
       */
      public void drawDomainTickBands(Graphics2D g2, Rectangle2D dataArea,
                                      List<ValueTick> ticks) {
          Paint bandPaint = getDomainTickBandPaint();
          if (bandPaint != null) {
              boolean fillBand = false;
              ValueAxis xAxis = getDomainAxis();
              double previous = xAxis.getLowerBound();
              for (ValueTick tick : ticks) {
                  double current = tick.getValue();
                  if (fillBand) {
                      getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea,
                              previous, current);
                  }
                  previous = current;
                  fillBand = !fillBand;
              }
              double end = xAxis.getUpperBound();
              if (fillBand) {
                  getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea,
                          previous, end);
              }
          }
      }
  
      /**
       * Draws the range tick bands, if any.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param ticks  the ticks.
       *
       * @see #setRangeTickBandPaint(Paint)
       */
      public void drawRangeTickBands(Graphics2D g2, Rectangle2D dataArea,
                                     List<ValueTick> ticks) {
          Paint bandPaint = getRangeTickBandPaint();
          if (bandPaint != null) {
              boolean fillBand = false;
              ValueAxis axis = getRangeAxis();
              double previous = axis.getLowerBound();
              for (ValueTick tick : ticks) {
                  double current = tick.getValue();
                  if (fillBand) {
                      getRenderer().fillRangeGridBand(g2, this, axis, dataArea,
                              previous, current);
                  }
                  previous = current;
                  fillBand = !fillBand;
              }
              double end = axis.getUpperBound();
              if (fillBand) {
                  getRenderer().fillRangeGridBand(g2, this, axis, dataArea,
                          previous, end);
              }
          }
      }
  
      /**
       * A utility method for drawing the axes.
       *
       * @param g2  the graphics device (<code>null</code> not permitted).
       * @param plotArea  the plot area (<code>null</code> not permitted).
       * @param dataArea  the data area (<code>null</code> not permitted).
       * @param plotState  collects information about the plot (<code>null</code>
       *                   permitted).
       *
       * @return A map containing the state for each axis drawn.
       */
      protected Map<Axis, AxisState> drawAxes(Graphics2D g2,
                             Rectangle2D plotArea,
                             Rectangle2D dataArea,
                             PlotRenderingInfo plotState) {
  
          AxisCollection axisCollection = new AxisCollection();
  
          // add domain axes to lists...
          for (int index = 0; index < this.domainAxes.size(); index++) {
              ValueAxis axis = this.domainAxes.get(index);
              if (axis != null) {
                  axisCollection.add(axis, getDomainAxisEdge(index));
              }
          }
  
          // add range axes to lists...
          for (int index = 0; index < this.rangeAxes.size(); index++) {
              ValueAxis yAxis = this.rangeAxes.get(index);
              if (yAxis != null) {
                  axisCollection.add(yAxis, getRangeAxisEdge(index));
              }
          }
  
          Map<Axis, AxisState> axisStateMap = new HashMap<Axis, AxisState>();
  
          // draw the top axes
          double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
                  dataArea.getHeight());
          for (Axis axis1 : axisCollection.getAxesAtTop()) {
              ValueAxis axis = (ValueAxis) axis1;
              AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
                      RectangleEdge.TOP, plotState);
              cursor = info.getCursor();
              axisStateMap.put(axis, info);
          }
  
          // draw the bottom axes
          cursor = dataArea.getMaxY()
                   + this.axisOffset.calculateBottomOutset(dataArea.getHeight());
          for (Axis axis1 : axisCollection.getAxesAtBottom()) {
              ValueAxis axis = (ValueAxis) axis1;
              AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
                      RectangleEdge.BOTTOM, plotState);
              cursor = info.getCursor();
              axisStateMap.put(axis, info);
          }
  
          // draw the left axes
          cursor = dataArea.getMinX()
                   - this.axisOffset.calculateLeftOutset(dataArea.getWidth());
          for (Axis axis1 : axisCollection.getAxesAtLeft()) {
              ValueAxis axis = (ValueAxis) axis1;
              AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
                      RectangleEdge.LEFT, plotState);
              cursor = info.getCursor();
              axisStateMap.put(axis, info);
          }
  
          // draw the right axes
          cursor = dataArea.getMaxX()
                   + this.axisOffset.calculateRightOutset(dataArea.getWidth());
          for (Axis axis1 : axisCollection.getAxesAtRight()) {
              ValueAxis axis = (ValueAxis) axis1;
              AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
                      RectangleEdge.RIGHT, plotState);
              cursor = info.getCursor();
              axisStateMap.put(axis, info);
          }
  
          return axisStateMap;
      }
  
      /**
       * Draws a representation of the data within the dataArea region, using the
       * current renderer.
       * <P>
       * The <code>info</code> and <code>crosshairState</code> arguments may be
       * <code>null</code>.
       *
       * @param g2  the graphics device.
       * @param dataArea  the region in which the data is to be drawn.
       * @param index  the dataset index.
       * @param info  an optional object for collection dimension information.
       * @param crosshairState  collects crosshair information
       *                        (<code>null</code> permitted).
       *
       * @return A flag that indicates whether any data was actually rendered.
       */
      public boolean render(Graphics2D g2, Rectangle2D dataArea, int index,
              PlotRenderingInfo info, CrosshairState crosshairState) {
  
          boolean foundData = false;
          XYDataset dataset = getDataset(index);
          if (!DatasetUtilities.isEmptyOrNull(dataset)) {
              foundData = true;
              ValueAxis xAxis = getDomainAxisForDataset(index);
              ValueAxis yAxis = getRangeAxisForDataset(index);
              if (xAxis == null || yAxis == null) {
                  return foundData;  // can't render anything without axes
              }
              XYItemRenderer renderer = getRenderer(index);
              if (renderer == null) {
                  renderer = getRenderer();
                  if (renderer == null) { // no default renderer available
                      return foundData;
                  }
              }
  
              XYItemRendererState state = renderer.initialise(g2, dataArea, this,
                      dataset, info);
              int passCount = renderer.getPassCount();
  
              SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();
              if (seriesOrder == SeriesRenderingOrder.REVERSE) {
                  //render series in reverse order
                  for (int pass = 0; pass < passCount; pass++) {
                      int seriesCount = dataset.getSeriesCount();
                      for (int series = seriesCount - 1; series >= 0; series--) {
                          int firstItem = 0;
                          int lastItem = dataset.getItemCount(series) - 1;
                          if (lastItem == -1) {
                              continue;
                          }
                          if (state.getProcessVisibleItemsOnly()) {
                              int[] itemBounds = RendererUtilities.findLiveItems(
                                      dataset, series, xAxis.getLowerBound(),
                                      xAxis.getUpperBound());
                              firstItem = Math.max(itemBounds[0] - 1, 0);
                              lastItem = Math.min(itemBounds[1] + 1, lastItem);
                          }
                          state.startSeriesPass(dataset, series, firstItem,
                                  lastItem, pass, passCount);
                          // TODO Bug: recalculate item count for real-time processing when data is updated frequently
                          for (int item = firstItem; item <= lastItem; item++) {
                              int lastItemLocal = dataset.getItemCount(series) - 1;
  
                              if (lastItemLocal < lastItem) {
                                  for (int itemLocal = item; itemLocal <= lastItemLocal; itemLocal++) {
                                      renderer.drawItem(g2, state, dataArea, info,
                                                        this, xAxis, yAxis, dataset, series, itemLocal,
                                                        crosshairState, pass);
                                  }
                              } else {
                                  renderer.drawItem(g2, state, dataArea, info,
                                                    this, xAxis, yAxis, dataset, series, item,
                                                    crosshairState, pass);
                              }
                          }
  
                          lastItem = dataset.getItemCount(series) - 1;
                          state.endSeriesPass(dataset, series, firstItem, lastItem, pass, passCount);
                      }
                  }
              }
              else {
                  //render series in forward order
                  for (int pass = 0; pass < passCount; pass++) {
                      int seriesCount = dataset.getSeriesCount();
                      for (int series = 0; series < seriesCount; series++) {
                          int firstItem = 0;
                          int lastItem = dataset.getItemCount(series) - 1;
                          if (state.getProcessVisibleItemsOnly()) {
                              int[] itemBounds = RendererUtilities.findLiveItems(
                                      dataset, series, xAxis.getLowerBound(),
                                      xAxis.getUpperBound());
                              firstItem = Math.max(itemBounds[0] - 1, 0);
                              lastItem = Math.min(itemBounds[1] + 1, lastItem);
                          }
                          state.startSeriesPass(dataset, series, firstItem,
                                  lastItem, pass, passCount);
  
                          // TODO Bug: recalculate item count for real-time processing when data is updated frequently
                          for (int item = firstItem; item <= lastItem; item++) {
                              int lastItemLocal = dataset.getItemCount(series) - 1;
  
                              if (lastItemLocal < lastItem) {
                                  for (int itemLocal = item; itemLocal <= lastItemLocal; itemLocal++) {
                                      renderer.drawItem(g2, state, dataArea, info,
                                                        this, xAxis, yAxis, dataset, series, itemLocal,
                                                        crosshairState, pass);
                                  }
                              } else {
                                  renderer.drawItem(g2, state, dataArea, info,
                                                    this, xAxis, yAxis, dataset, series, item,
                                                    crosshairState, pass);
                              }
                          }
  
                          lastItem = dataset.getItemCount(series) - 1;
                          state.endSeriesPass(dataset, series, firstItem, lastItem, pass, passCount);
                      }
                  }
              }
          }
          return foundData;
      }
  
      /**
       * Returns the domain axis for a dataset.
       *
       * @param index  the dataset index.
       *
       * @return The axis.
       */
      public ValueAxis getDomainAxisForDataset(int index) {
          int upper = Math.max(getDatasetCount(), getRendererCount());
          if (index < 0 || index >= upper) {
              throw new IllegalArgumentException("Index " + index
                      + " out of bounds.");
          }
          ValueAxis valueAxis;
          List<Integer> axisIndices = this.datasetToDomainAxesMap.get(
                  index);
          if (axisIndices != null) {
              // the first axis in the list is used for data <--> Java2D
              Integer axisIndex = axisIndices.get(0);
              valueAxis = getDomainAxis(axisIndex);
          }
          else {
              valueAxis = getDomainAxis(0);
          }
          return valueAxis;
      }
  
      /**
       * Returns the range axis for a dataset.
       *
       * @param index  the dataset index.
       *
       * @return The axis.
       */
      public ValueAxis getRangeAxisForDataset(int index) {
          int upper = Math.max(getDatasetCount(), getRendererCount());
          if (index < 0 || index >= upper) {
              throw new IllegalArgumentException("Index " + index
                      + " out of bounds.");
          }
          ValueAxis valueAxis;
          List<Integer> axisIndices = this.datasetToRangeAxesMap.get(
                  index);
          if (axisIndices != null) {
              // the first axis in the list is used for data <--> Java2D
              Integer axisIndex = axisIndices.get(0);
              valueAxis = getRangeAxis(axisIndex);
          }
          else {
              valueAxis = getRangeAxis(0);
          }
          return valueAxis;
      }
  
      /**
       * Draws the gridlines for the plot, if they are visible.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param ticks  the ticks.
       *
       * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List)
       */
      protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea,
                                         List<ValueTick> ticks) {
  
          // no renderer, no gridlines...
          if (getRenderer() == null) {
              return;
          }
  
          // draw the domain grid lines, if any...
          if (isDomainGridlinesVisible() || isDomainMinorGridlinesVisible()) {
              Stroke gridStroke = null;
              Paint gridPaint = null;
              boolean paintLine;
              for (ValueTick tick : ticks) {
                  paintLine = false;
                  if ((tick.getTickType() == TickType.MINOR)
                          && isDomainMinorGridlinesVisible()) {
                      gridStroke = getDomainMinorGridlineStroke();
                      gridPaint = getDomainMinorGridlinePaint();
                      paintLine = true;
                  } else if ((tick.getTickType() == TickType.MAJOR)
                          && isDomainGridlinesVisible()) {
                      gridStroke = getDomainGridlineStroke();
                      gridPaint = getDomainGridlinePaint();
                      paintLine = true;
                  }
                  XYItemRenderer r = getRenderer();
                  if ((r instanceof AbstractXYItemRenderer) && paintLine) {
                      ((AbstractXYItemRenderer) r).drawDomainLine(g2, this,
                              getDomainAxis(), dataArea, tick.getValue(),
                              gridPaint, gridStroke);
                  }
              }
          }
      }
  
      /**
       * Draws the gridlines for the plot's primary range axis, if they are
       * visible.
       *
       * @param g2  the graphics device.
       * @param area  the data area.
       * @param ticks  the ticks.
       *
       * @see #drawDomainGridlines(Graphics2D, Rectangle2D, List)
       */
      protected void drawRangeGridlines(Graphics2D g2, Rectangle2D area,
                                        List<ValueTick> ticks) {
  
          // no renderer, no gridlines...
          if (getRenderer() == null) {
              return;
          }
  
          // draw the range grid lines, if any...
          if (isRangeGridlinesVisible() || isRangeMinorGridlinesVisible()) {
              Stroke gridStroke = null;
              Paint gridPaint = null;
              ValueAxis axis = getRangeAxis();
              if (axis != null) {
                  boolean paintLine;
                  for (ValueTick tick : ticks) {
                      paintLine = false;
                      if ((tick.getTickType() == TickType.MINOR)
                              && isRangeMinorGridlinesVisible()) {
                          gridStroke = getRangeMinorGridlineStroke();
                          gridPaint = getRangeMinorGridlinePaint();
                          paintLine = true;
                      } else if ((tick.getTickType() == TickType.MAJOR)
                              && isRangeGridlinesVisible()) {
                          gridStroke = getRangeGridlineStroke();
                          gridPaint = getRangeGridlinePaint();
                          paintLine = true;
                      }
                      if ((tick.getValue() != 0.0
                              || !isRangeZeroBaselineVisible()) && paintLine) {
                          getRenderer().drawRangeGridline(g2, this, getRangeAxis(),
                                  area, tick.getValue(), gridPaint, gridStroke);
                      }
                  }
              }
          }
      }
  
      /**
       * Draws a base line across the chart at value zero on the domain axis.
       *
       * @param g2  the graphics device.
       * @param area  the data area.
       *
       * @see #setDomainZeroBaselineVisible(boolean)
       *
       * @since 1.0.5
       */
      protected void drawZeroDomainBaseline(Graphics2D g2, Rectangle2D area) {
          if (isDomainZeroBaselineVisible()) {
              XYItemRenderer r = getRenderer();
              // FIXME: the renderer interface doesn't have the drawDomainLine()
              // method, so we have to rely on the renderer being a subclass of
              // AbstractXYItemRenderer (which is lame)
              if (r instanceof AbstractXYItemRenderer) {
                  AbstractXYItemRenderer renderer = (AbstractXYItemRenderer) r;
                  renderer.drawDomainLine(g2, this, getDomainAxis(), area, 0.0,
                          this.domainZeroBaselinePaint,
                          this.domainZeroBaselineStroke);
              }
          }
      }
  
      /**
       * Draws a base line across the chart at value zero on the range axis.
       *
       * @param g2  the graphics device.
       * @param area  the data area.
       *
       * @see #setRangeZeroBaselineVisible(boolean)
       */
      protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) {
          if (isRangeZeroBaselineVisible()) {
              getRenderer().drawRangeGridline(g2, this, getRangeAxis(), area, 0.0,
                      this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke);
          }
      }
  
      /**
       * Draws the annotations for the plot.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param info  the chart rendering info.
       */
      public void drawAnnotations(Graphics2D g2, Rectangle2D dataArea,
                                  PlotRenderingInfo info) {
          ValueAxis xAxis = getDomainAxis();
          ValueAxis yAxis = getRangeAxis();
          for (XYAnnotation annotation : this.annotations) {
              annotation.draw(g2, this, dataArea, xAxis, yAxis, 0, info);
          }
      }
  
      /**
       * Draws the domain markers (if any) for an axis and layer.  This method is
       * typically called from within the draw() method.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param index  the renderer index.
       * @param layer  the layer (foreground or background).
       */
      protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea,
                                       int index, Layer layer) {
  
          XYItemRenderer r = getRenderer(index);
          if (r == null) {
              return;
          }
          // check that the renderer has a corresponding dataset (it doesn't
          // matter if the dataset is null)
          if (index >= getDatasetCount()) {
              return;
          }
          Collection<Marker> markers = getDomainMarkers(index, layer);
          ValueAxis axis = getDomainAxisForDataset(index);
          if (markers != null && axis != null) {
              for (Marker marker : markers) {
                  r.drawDomainMarker(g2, this, axis, marker, dataArea);
              }
          }
  
      }
  
      /**
       * Draws the range markers (if any) for a renderer and layer.  This method
       * is typically called from within the draw() method.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param index  the renderer index.
       * @param layer  the layer (foreground or background).
       */
      protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea,
                                      int index, Layer layer) {
  
          XYItemRenderer r = getRenderer(index);
          if (r == null) {
              return;
          }
          // check that the renderer has a corresponding dataset (it doesn't
          // matter if the dataset is null)
          if (index >= getDatasetCount()) {
              return;
          }
          Collection<Marker> markers = getRangeMarkers(index, layer);
          ValueAxis axis = getRangeAxisForDataset(index);
          if (markers != null && axis != null) {
              for (Marker marker : markers) {
                  r.drawRangeMarker(g2, this, axis, marker, dataArea);
              }
          }
      }
  
      /**
       * Returns the list of domain markers (read only) for the specified layer.
       *
       * @param layer  the layer (foreground or background).
       *
       * @return The list of domain markers.
       *
       * @see #getRangeMarkers(Layer)
       */
      public Collection<Marker> getDomainMarkers(Layer layer) {
          return getDomainMarkers(0, layer);
      }
  
      /**
       * Returns the list of range markers (read only) for the specified layer.
       *
       * @param layer  the layer (foreground or background).
       *
       * @return The list of range markers.
       *
       * @see #getDomainMarkers(Layer)
       */
      public Collection<Marker> getRangeMarkers(Layer layer) {
          return getRangeMarkers(0, layer);
      }
  
      /**
       * Returns a collection of domain markers for a particular renderer and
       * layer.
       *
       * @param index  the renderer index.
       * @param layer  the layer.
       *
       * @return A collection of markers (possibly <code>null</code>).
       *
       * @see #getRangeMarkers(int, Layer)
       */
      public Collection<Marker> getDomainMarkers(int index, Layer layer) {
          Collection<Marker> result = null;
          Integer key = index;
          if (layer == Layer.FOREGROUND) {
              result = this.foregroundDomainMarkers.get(key);
          }
          else if (layer == Layer.BACKGROUND) {
              result = this.backgroundDomainMarkers.get(key);
          }
          if (result != null) {
              result = Collections.unmodifiableCollection(result);
          }
          return result;
      }
  
      /**
       * Returns a collection of range markers for a particular renderer and
       * layer.
       *
       * @param index  the renderer index.
       * @param layer  the layer.
       *
       * @return A collection of markers (possibly <code>null</code>).
       *
       * @see #getDomainMarkers(int, Layer)
       */
      public Collection<Marker> getRangeMarkers(int index, Layer layer) {
          Collection<Marker> result = null;
          Integer key = index;
          if (layer == Layer.FOREGROUND) {
              result = this.foregroundRangeMarkers.get(key);
          }
          else if (layer == Layer.BACKGROUND) {
              result = this.backgroundRangeMarkers.get(key);
          }
          if (result != null) {
              result = Collections.unmodifiableCollection(result);
          }
          return result;
      }
  
      /**
       * Utility method for drawing a horizontal line across the data area of the
       * plot.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param value  the coordinate, where to draw the line.
       * @param stroke  the stroke to use.
       * @param paint  the paint to use.
       */
      protected void drawHorizontalLine(Graphics2D g2, Rectangle2D dataArea,
                                        double value, Stroke stroke,
                                        Paint paint) {
  
          ValueAxis axis = getRangeAxis();
          if (getOrientation() == PlotOrientation.HORIZONTAL) {
              axis = getDomainAxis();
          }
          if (axis.getRange().contains(value)) {
              double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT);
              Line2D line = new Line2D.Double(dataArea.getMinX(), yy,
                      dataArea.getMaxX(), yy);
              g2.setStroke(stroke);
              g2.setPaint(paint);
              g2.draw(line);
          }
  
      }
  
      /**
       * Draws a domain crosshair.
       *
       * @param g2  the graphics target.
       * @param dataArea  the data area.
       * @param orientation  the plot orientation.
       * @param value  the crosshair value.
       * @param axis  the axis against which the value is measured.
       * @param stroke  the stroke used to draw the crosshair line.
       * @param paint  the paint used to draw the crosshair line.
       *
       * @since 1.0.4
       */
      protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea,
              PlotOrientation orientation, double value, ValueAxis axis,
              Stroke stroke, Paint paint) {
  
          if (axis.getRange().contains(value)) {
              Line2D line;
              if (orientation == PlotOrientation.VERTICAL) {
                  double xx = axis.valueToJava2D(value, dataArea,
                          RectangleEdge.BOTTOM);
                  line = new Line2D.Double(xx, dataArea.getMinY(), xx,
                          dataArea.getMaxY());
              }
              else {
                  double yy = axis.valueToJava2D(value, dataArea,
                          RectangleEdge.LEFT);
                  line = new Line2D.Double(dataArea.getMinX(), yy,
                          dataArea.getMaxX(), yy);
              }
              g2.setStroke(stroke);
              g2.setPaint(paint);
              g2.draw(line);
          }
  
      }
  
      /**
       * Utility method for drawing a vertical line on the data area of the plot.
       *
       * @param g2  the graphics device.
       * @param dataArea  the data area.
       * @param value  the coordinate, where to draw the line.
       * @param stroke  the stroke to use.
       * @param paint  the paint to use.
       */
      protected void drawVerticalLine(Graphics2D g2, Rectangle2D dataArea,
                                      double value, Stroke stroke, Paint paint) {
  
          ValueAxis axis = getDomainAxis();
          if (getOrientation() == PlotOrientation.HORIZONTAL) {
              axis = getRangeAxis();
          }
          if (axis.getRange().contains(value)) {
              double xx = axis.valueToJava2D(value, dataArea,
                      RectangleEdge.BOTTOM);
              Line2D line = new Line2D.Double(xx, dataArea.getMinY(), xx,
                      dataArea.getMaxY());
              g2.setStroke(stroke);
              g2.setPaint(paint);
              g2.draw(line);
          }
  
      }
  
      /**
       * Draws a range crosshair.
       *
       * @param g2  the graphics target.
       * @param dataArea  the data area.
       * @param orientation  the plot orientation.
       * @param value  the crosshair value.
       * @param axis  the axis against which the value is measured.
       * @param stroke  the stroke used to draw the crosshair line.
       * @param paint  the paint used to draw the crosshair line.
       *
       * @since 1.0.4
       */
      protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea,
              PlotOrientation orientation, double value, ValueAxis axis,
              Stroke stroke, Paint paint) {
  
          if (axis.getRange().contains(value)) {
              Line2D line;
              if (orientation == PlotOrientation.HORIZONTAL) {
                  double xx = axis.valueToJava2D(value, dataArea,
                          RectangleEdge.BOTTOM);
                  line = new Line2D.Double(xx, dataArea.getMinY(), xx,
                          dataArea.getMaxY());
              }
              else {
                  double yy = axis.valueToJava2D(value, dataArea,
                          RectangleEdge.LEFT);
                  line = new Line2D.Double(dataArea.getMinX(), yy,
                          dataArea.getMaxX(), yy);
              }
              g2.setStroke(stroke);
              g2.setPaint(paint);
              g2.draw(line);
          }
  
      }
  
      /**
       * Handles a 'click' on the plot by updating the anchor values.
       *
       * @param x  the x-coordinate, where the click occurred, in Java2D space.
       * @param y  the y-coordinate, where the click occurred, in Java2D space.
       * @param info  object containing information about the plot dimensions.
       */
      @Override
      public void handleClick(int x, int y, PlotRenderingInfo info) {
  
          Rectangle2D dataArea = info.getDataArea();
          if (dataArea.contains(x, y)) {
              // set the anchor value for the horizontal axis...
              ValueAxis xaxis = getDomainAxis();
              if (xaxis != null) {
                  double hvalue = xaxis.java2DToValue(x, info.getDataArea(),
                          getDomainAxisEdge());
                  setDomainCrosshairValue(hvalue);
              }
  
              // set the anchor value for the vertical axis...
              ValueAxis yaxis = getRangeAxis();
              if (yaxis != null) {
                  double vvalue = yaxis.java2DToValue(y, info.getDataArea(),
                          getRangeAxisEdge());
                  setRangeCrosshairValue(vvalue);
              }
          }
      }
  
      /**
       * A utility method that returns a list of datasets that are mapped to a
       * particular axis.
       *
       * @param axisIndex  the axis index (<code>null</code> not permitted).
       *
       * @return A list of datasets.
       */
      private List<XYDataset> getDatasetsMappedToDomainAxis(Integer axisIndex) {
          ParamChecks.nullNotPermitted(axisIndex, "axisIndex");
          List<XYDataset> result = new ArrayList<XYDataset>();
          for (int i = 0; i < this.datasets.size(); i++) {
              List<Integer> mappedAxes = this.datasetToDomainAxesMap.get(
                      i);
              if (mappedAxes == null) {
                  if (axisIndex.equals(ZERO)) {
                      result.add(this.datasets.get(i));
                  }
              }
              else {
                  if (mappedAxes.contains(axisIndex)) {
                      result.add(this.datasets.get(i));
                  }
              }
          }
          return result;
      }
  
      /**
       * A utility method that returns a list of datasets that are mapped to a
       * particular axis.
       *
       * @param axisIndex  the axis index (<code>null</code> not permitted).
       *
       * @return A list of datasets.
       */
      private List<XYDataset> getDatasetsMappedToRangeAxis(Integer axisIndex) {
          ParamChecks.nullNotPermitted(axisIndex, "axisIndex");
          List<XYDataset> result = new ArrayList<XYDataset>();
          for (int i = 0; i < this.datasets.size(); i++) {
              List<Integer> mappedAxes = this.datasetToRangeAxesMap.get(
                      i);
              if (mappedAxes == null) {
                  if (axisIndex.equals(ZERO)) {
                      result.add(this.datasets.get(i));
                  }
              }
              else {
                  if (mappedAxes.contains(axisIndex)) {
                      result.add(this.datasets.get(i));
                  }
              }
          }
          return result;
      }
  
      /**
       * Returns the index of the given domain axis.
       *
       * @param axis  the axis.
       *
       * @return The axis index.
       *
       * @see #getRangeAxisIndex(ValueAxis)
       */
      public int getDomainAxisIndex(ValueAxis axis) {
          int result = this.domainAxes.indexOf(axis);
          if (result < 0) {
              // try the parent plot
              Plot parent = getParent();
              if (parent instanceof XYPlot) {
                  XYPlot p = (XYPlot) parent;
                  result = p.getDomainAxisIndex(axis);
              }
          }
          return result;
      }
  
      /**
       * Returns the index of the given range axis.
       *
       * @param axis  the axis.
       *
       * @return The axis index.
       *
       * @see #getDomainAxisIndex(ValueAxis)
       */
      public int getRangeAxisIndex(ValueAxis axis) {
          int result = this.rangeAxes.indexOf(axis);
          if (result < 0) {
              // try the parent plot
              Plot parent = getParent();
              if (parent instanceof XYPlot) {
                  XYPlot p = (XYPlot) parent;
                  result = p.getRangeAxisIndex(axis);
              }
          }
          return result;
      }
  
      /**
       * Returns the range for the specified axis.
       *
       * @param axis  the axis.
       *
       * @return The range.
       */
      @Override
      public Range getDataRange(ValueAxis axis) {
  
          Range result = null;
          List<XYDataset> mappedDatasets = new ArrayList<XYDataset>();
          List<XYAnnotation> includedAnnotations = new ArrayList<XYAnnotation>();
          boolean isDomainAxis = true;
  
          // is it a domain axis?
          int domainIndex = getDomainAxisIndex(axis);
          if (domainIndex >= 0) {
              isDomainAxis = true;
              mappedDatasets.addAll(getDatasetsMappedToDomainAxis(
                      domainIndex));
              if (domainIndex == 0) {
                  // grab the plot's annotations
                  for (XYAnnotation annotation : this.annotations) {
                      if (annotation instanceof XYAnnotationBoundsInfo) {
                          includedAnnotations.add(annotation);
                      }
                  }
              }
          }
  
          // or is it a range axis?
          int rangeIndex = getRangeAxisIndex(axis);
          if (rangeIndex >= 0) {
              isDomainAxis = false;
              mappedDatasets.addAll(getDatasetsMappedToRangeAxis(
                      rangeIndex));
              if (rangeIndex == 0) {
                  for (XYAnnotation annotation : this.annotations) {
                      if (annotation instanceof XYAnnotationBoundsInfo) {
                          includedAnnotations.add(annotation);
                      }
                  }
              }
          }
  
          // iterate through the datasets that map to the axis and get the union
          // of the ranges.
          for (XYDataset d : mappedDatasets) {
              if (d != null) {
                  XYItemRenderer r = getRendererForDataset(d);
                  if (r != null) {
                      if (isDomainAxis) {
                          result = Range.combine(result, r.findDomainBounds(d));
                      } else {
                          result = Range.combine(result, r.findRangeBounds(d));
                      }
                      Collection<XYAnnotation> c = r.getAnnotations();
                      for (XYAnnotation a : c) {
                          if (a instanceof XYAnnotationBoundsInfo) {
                              includedAnnotations.add(a);
                          }
                      }
                  } else { // renderer is null
                      if (isDomainAxis) {
                          result = Range.combine(result,
                                  DatasetUtilities.findDomainBounds(d));
                      } else {
                          result = Range.combine(result,
                                  DatasetUtilities.findRangeBounds(d));
                      }
  
                  }
              }
          }
  
          for (XYAnnotation includedAnnotation : includedAnnotations) {
              XYAnnotationBoundsInfo xyabi = (XYAnnotationBoundsInfo) includedAnnotation;
              if (xyabi.getIncludeInDataBounds()) {
                  if (isDomainAxis) {
                      result = Range.combine(result, xyabi.getXRange());
                  } else {
                      result = Range.combine(result, xyabi.getYRange());
                  }
              }
          }
  
          return result;
  
      }
  
      /**
       * Receives notification of a change to an {@link Annotation} added to
       * this plot.
       *
       * @param event  information about the event (not used here).
       *
       * @since 1.0.14
       */
      @Override
      public void annotationChanged(AnnotationChangeEvent event) {
          if (getParent() != null) {
              getParent().annotationChanged(event);
          }
          else {
              PlotChangeEvent e = new PlotChangeEvent(this);
              notifyListeners(e);
          }
      }
  
      /**
       * Receives notification of a change to the plot's dataset.
       * <P>
       * The axis ranges are updated if necessary.
       *
       * @param event  information about the event (not used here).
       */
      @Override
      public void datasetChanged(DatasetChangeEvent event) {
          configureDomainAxes();
          configureRangeAxes();
          if (getParent() != null) {
              getParent().datasetChanged(event);
          }
          else {
              PlotChangeEvent e = new PlotChangeEvent(this);
              e.setType(ChartChangeEventType.DATASET_UPDATED);
              notifyListeners(e);
          }
      }
  
      /**
       * Receives notification of a renderer change event.
       *
       * @param event  the event.
       */
      @Override
      public void rendererChanged(RendererChangeEvent event) {
          // if the event was caused by a change to series visibility, then
          // the axis ranges might need updating...
          if (event.getSeriesVisibilityChanged()) {
              configureDomainAxes();
              configureRangeAxes();
          }
          fireChangeEvent();
      }
  
      /**
       * Returns a flag indicating whether or not the domain crosshair is visible.
       *
       * @return The flag.
       *
       * @see #setDomainCrosshairVisible(boolean)
       */
      public boolean isDomainCrosshairVisible() {
          return this.domainCrosshairVisible;
      }
  
      /**
       * Sets the flag indicating whether or not the domain crosshair is visible
       * and, if the flag changes, sends a {@link PlotChangeEvent} to all
       * registered listeners.
       *
       * @param flag  the new value of the flag.
       *
       * @see #isDomainCrosshairVisible()
       */
      public void setDomainCrosshairVisible(boolean flag) {
          if (this.domainCrosshairVisible != flag) {
              this.domainCrosshairVisible = flag;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns a flag indicating whether or not the crosshair should "lock-on"
       * to actual data values.
       *
       * @return The flag.
       *
       * @see #setDomainCrosshairLockedOnData(boolean)
       */
      public boolean isDomainCrosshairLockedOnData() {
          return this.domainCrosshairLockedOnData;
      }
  
      /**
       * Sets the flag indicating whether or not the domain crosshair should
       * "lock-on" to actual data values.  If the flag value changes, this
       * method sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param flag  the flag.
       *
       * @see #isDomainCrosshairLockedOnData()
       */
      public void setDomainCrosshairLockedOnData(boolean flag) {
          if (this.domainCrosshairLockedOnData != flag) {
              this.domainCrosshairLockedOnData = flag;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the domain crosshair value.
       *
       * @return The value.
       *
       * @see #setDomainCrosshairValue(double)
       */
      public double getDomainCrosshairValue() {
          return this.domainCrosshairValue;
      }
  
      /**
       * Sets the domain crosshair value and sends a {@link PlotChangeEvent} to
       * all registered listeners (provided that the domain crosshair is visible).
       *
       * @param value  the value.
       *
       * @see #getDomainCrosshairValue()
       */
      public void setDomainCrosshairValue(double value) {
          setDomainCrosshairValue(value, true);
      }
  
      /**
       * Sets the domain crosshair value and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners (provided that the
       * domain crosshair is visible).
       *
       * @param value  the new value.
       * @param notify  notify listeners?
       *
       * @see #getDomainCrosshairValue()
       */
      public void setDomainCrosshairValue(double value, boolean notify) {
          this.domainCrosshairValue = value;
          if (isDomainCrosshairVisible() && notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the {@link Stroke} used to draw the crosshair (if visible).
       *
       * @return The crosshair stroke (never <code>null</code>).
       *
       * @see #setDomainCrosshairStroke(Stroke)
       * @see #isDomainCrosshairVisible()
       * @see #getDomainCrosshairPaint()
       */
      public Stroke getDomainCrosshairStroke() {
          return this.domainCrosshairStroke;
      }
  
      /**
       * Sets the Stroke used to draw the crosshairs (if visible) and notifies
       * registered listeners that the axis has been modified.
       *
       * @param stroke  the new crosshair stroke (<code>null</code> not
       *     permitted).
       *
       * @see #getDomainCrosshairStroke()
       */
      public void setDomainCrosshairStroke(Stroke stroke) {
          ParamChecks.nullNotPermitted(stroke, "stroke");
          this.domainCrosshairStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the domain crosshair paint.
       *
       * @return The crosshair paint (never <code>null</code>).
       *
       * @see #setDomainCrosshairPaint(Paint)
       * @see #isDomainCrosshairVisible()
       * @see #getDomainCrosshairStroke()
       */
      public Paint getDomainCrosshairPaint() {
          return this.domainCrosshairPaint;
      }
  
      /**
       * Sets the paint used to draw the crosshairs (if visible) and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint the new crosshair paint (<code>null</code> not permitted).
       *
       * @see #getDomainCrosshairPaint()
       */
      public void setDomainCrosshairPaint(Paint paint) {
          if (paint == null) {
              throw new IllegalArgumentException("Null 'paint' argument.");
          }
          this.domainCrosshairPaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns a flag indicating whether or not the range crosshair is visible.
       *
       * @return The flag.
       *
       * @see #setRangeCrosshairVisible(boolean)
       * @see #isDomainCrosshairVisible()
       */
      public boolean isRangeCrosshairVisible() {
          return this.rangeCrosshairVisible;
      }
  
      /**
       * Sets the flag indicating whether or not the range crosshair is visible.
       * If the flag value changes, this method sends a {@link PlotChangeEvent}
       * to all registered listeners.
       *
       * @param flag  the new value of the flag.
       *
       * @see #isRangeCrosshairVisible()
       */
      public void setRangeCrosshairVisible(boolean flag) {
          if (this.rangeCrosshairVisible != flag) {
              this.rangeCrosshairVisible = flag;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns a flag indicating whether or not the crosshair should "lock-on"
       * to actual data values.
       *
       * @return The flag.
       *
       * @see #setRangeCrosshairLockedOnData(boolean)
       */
      public boolean isRangeCrosshairLockedOnData() {
          return this.rangeCrosshairLockedOnData;
      }
  
      /**
       * Sets the flag indicating whether or not the range crosshair should
       * "lock-on" to actual data values.  If the flag value changes, this method
       * sends a {@link PlotChangeEvent} to all registered listeners.
       *
       * @param flag  the flag.
       *
       * @see #isRangeCrosshairLockedOnData()
       */
      public void setRangeCrosshairLockedOnData(boolean flag) {
          if (this.rangeCrosshairLockedOnData != flag) {
              this.rangeCrosshairLockedOnData = flag;
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the range crosshair value.
       *
       * @return The value.
       *
       * @see #setRangeCrosshairValue(double)
       */
      public double getRangeCrosshairValue() {
          return this.rangeCrosshairValue;
      }
  
      /**
       * Sets the range crosshair value.
       * <P>
       * Registered listeners are notified that the plot has been modified, but
       * only if the crosshair is visible.
       *
       * @param value  the new value.
       *
       * @see #getRangeCrosshairValue()
       */
      public void setRangeCrosshairValue(double value) {
          setRangeCrosshairValue(value, true);
      }
  
      /**
       * Sets the range crosshair value and sends a {@link PlotChangeEvent} to
       * all registered listeners, but only if the crosshair is visible.
       *
       * @param value  the new value.
       * @param notify  a flag that controls whether or not listeners are
       *                notified.
       *
       * @see #getRangeCrosshairValue()
       */
      public void setRangeCrosshairValue(double value, boolean notify) {
          this.rangeCrosshairValue = value;
          if (isRangeCrosshairVisible() && notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the stroke used to draw the crosshair (if visible).
       *
       * @return The crosshair stroke (never <code>null</code>).
       *
       * @see #setRangeCrosshairStroke(Stroke)
       * @see #isRangeCrosshairVisible()
       * @see #getRangeCrosshairPaint()
       */
      public Stroke getRangeCrosshairStroke() {
          return this.rangeCrosshairStroke;
      }
  
      /**
       * Sets the stroke used to draw the crosshairs (if visible) and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param stroke  the new crosshair stroke (<code>null</code> not
       *         permitted).
       *
       * @see #getRangeCrosshairStroke()
       */
      public void setRangeCrosshairStroke(Stroke stroke) {
          ParamChecks.nullNotPermitted(stroke, "stroke");
          this.rangeCrosshairStroke = stroke;
          fireChangeEvent();
      }
  
      /**
       * Returns the range crosshair paint.
       *
       * @return The crosshair paint (never <code>null</code>).
       *
       * @see #setRangeCrosshairPaint(Paint)
       * @see #isRangeCrosshairVisible()
       * @see #getRangeCrosshairStroke()
       */
      public Paint getRangeCrosshairPaint() {
          return this.rangeCrosshairPaint;
      }
  
      /**
       * Sets the paint used to color the crosshairs (if visible) and sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param paint the new crosshair paint (<code>null</code> not permitted).
       *
       * @see #getRangeCrosshairPaint()
       */
      public void setRangeCrosshairPaint(Paint paint) {
          if (paint == null) {
              throw new IllegalArgumentException("Null 'paint' argument.");
          }
          this.rangeCrosshairPaint = paint;
          fireChangeEvent();
      }
  
      /**
       * Returns the fixed domain axis space.
       *
       * @return The fixed domain axis space (possibly <code>null</code>).
       *
       * @see #setFixedDomainAxisSpace(AxisSpace)
       */
      public AxisSpace getFixedDomainAxisSpace() {
          return this.fixedDomainAxisSpace;
      }
  
      /**
       * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param space  the space (<code>null</code> permitted).
       *
       * @see #getFixedDomainAxisSpace()
       */
      public void setFixedDomainAxisSpace(AxisSpace space) {
          setFixedDomainAxisSpace(space, true);
      }
  
      /**
       * Sets the fixed domain axis space and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param space  the space (<code>null</code> permitted).
       * @param notify  notify listeners?
       *
       * @see #getFixedDomainAxisSpace()
       *
       * @since 1.0.9
       */
      public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) {
          this.fixedDomainAxisSpace = space;
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Returns the fixed range axis space.
       *
       * @return The fixed range axis space (possibly <code>null</code>).
       *
       * @see #setFixedRangeAxisSpace(AxisSpace)
       */
      public AxisSpace getFixedRangeAxisSpace() {
          return this.fixedRangeAxisSpace;
      }
  
      /**
       * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
       * all registered listeners.
       *
       * @param space  the space (<code>null</code> permitted).
       *
       * @see #getFixedRangeAxisSpace()
       */
      public void setFixedRangeAxisSpace(AxisSpace space) {
          setFixedRangeAxisSpace(space, true);
      }
  
      /**
       * Sets the fixed range axis space and, if requested, sends a
       * {@link PlotChangeEvent} to all registered listeners.
       *
       * @param space  the space (<code>null</code> permitted).
       * @param notify  notify listeners?
       *
       * @see #getFixedRangeAxisSpace()
       *
       * @since 1.0.9
       */
      public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) {
          this.fixedRangeAxisSpace = space;
          if (notify) {
              fireChangeEvent();
          }
      }
  
      /**
       * Returns <code>true</code> if panning is enabled for the domain axes,
       * and <code>false</code> otherwise.
       *
       * @return A boolean.
       *
       * @since 1.0.13
       */
      @Override
      public boolean isDomainPannable() {
          return this.domainPannable;
      }
  
      /**
       * Sets the flag that enables or disables panning of the plot along the
       * domain axes.
       *
       * @param pannable  the new flag value.
       *
       * @since 1.0.13
       */
      public void setDomainPannable(boolean pannable) {
          this.domainPannable = pannable;
      }
  
      /**
       * Returns <code>true</code> if panning is enabled for the range axes,
       * and <code>false</code> otherwise.
       *
       * @return A boolean.
       *
       * @since 1.0.13
       */
      @Override
      public boolean isRangePannable() {
          return this.rangePannable;
      }
  
      /**
       * Sets the flag that enables or disables panning of the plot along
       * the range axes.
       *
       * @param pannable  the new flag value.
       *
       * @since 1.0.13
       */
      public void setRangePannable(boolean pannable) {
          this.rangePannable = pannable;
      }
  
      /**
       * Pans the domain axes by the specified percentage.
       *
       * @param percent  the distance to pan (as a percentage of the axis length).
       * @param info the plot info
       * @param source the source point where the pan action started.
       *
       * @since 1.0.13
       */
      @Override
      public void panDomainAxes(double percent, PlotRenderingInfo info,
              Point2D source) {
          if (!isDomainPannable()) {
              return;
          }
          int domainAxisCount = getDomainAxisCount();
          for (int i = 0; i < domainAxisCount; i++) {
              ValueAxis axis = getDomainAxis(i);
              if (axis == null) {
                  continue;
              }
              if (axis.isInverted()) {
                  percent = -percent;
              }
              axis.pan(percent);
          }
      }
  
      /**
       * Pans the range axes by the specified percentage.
       *
       * @param percent  the distance to pan (as a percentage of the axis length).
       * @param info the plot info
       * @param source the source point where the pan action started.
       *
       * @since 1.0.13
       */
      @Override
      public void panRangeAxes(double percent, PlotRenderingInfo info,
              Point2D source) {
          if (!isRangePannable()) {
              return;
          }
          int rangeAxisCount = getRangeAxisCount();
          for (int i = 0; i < rangeAxisCount; i++) {
              ValueAxis axis = getRangeAxis(i);
              if (axis == null) {
                  continue;
              }
              if (axis.isInverted()) {
                  percent = -percent;
              }
              axis.pan(percent);
          }
      }
  
      /**
       * Multiplies the range on the domain axis/axes by the specified factor.
       *
       * @param factor  the zoom factor.
       * @param info  the plot rendering info.
       * @param source  the source point (in Java2D space).
       *
       * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D)
       */
      @Override
      public void zoomDomainAxes(double factor, PlotRenderingInfo info,
                                 Point2D source) {
          // delegate to other method
          zoomDomainAxes(factor, info, source, false);
      }
  
      /**
       * Multiplies the range on the domain axis/axes by the specified factor.
       *
       * @param factor  the zoom factor.
       * @param info  the plot rendering info.
       * @param source  the source point (in Java2D space).
       * @param useAnchor  use source point as zoom anchor?
       *
       * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean)
       *
       * @since 1.0.7
       */
      @Override
      public void zoomDomainAxes(double factor, PlotRenderingInfo info,
                                 Point2D source, boolean useAnchor) {
  
          // perform the zoom on each domain axis
          for (int i = 0; i < this.domainAxes.size(); i++) {
              ValueAxis domainAxis = this.domainAxes.get(i);
              if (domainAxis != null) {
                  if (useAnchor) {
                      // get the relevant source coordinate given the plot
                      // orientation
                      double sourceX = source.getX();
                      if (this.orientation == PlotOrientation.HORIZONTAL) {
                          sourceX = source.getY();
                      }
                      double anchorX = domainAxis.java2DToValue(sourceX,
                              info.getDataArea(), getDomainAxisEdge());
                      domainAxis.resizeRange2(factor, anchorX);
                  }
                  else {
                      domainAxis.resizeRange(factor);
                  }
              }
          }
      }
  
      /**
       * Zooms in on the domain axis/axes.  The new lower and upper bounds are
       * specified as percentages of the current axis range, where 0 percent is
       * the current lower bound and 100 percent is the current upper bound.
       *
       * @param lowerPercent  a percentage that determines the new lower bound
       *                      for the axis (e.g. 0.20 is twenty percent).
       * @param upperPercent  a percentage that determines the new upper bound
       *                      for the axis (e.g. 0.80 is eighty percent).
       * @param info  the plot rendering info.
       * @param source  the source point (ignored).
       *
       * @see #zoomRangeAxes(double, double, PlotRenderingInfo, Point2D)
       */
      @Override
      public void zoomDomainAxes(double lowerPercent, double upperPercent,
                                 PlotRenderingInfo info, Point2D source) {
          for (int i = 0; i < this.domainAxes.size(); i++) {
              ValueAxis domainAxis = this.domainAxes.get(i);
              if (domainAxis != null) {
                  domainAxis.zoomRange(lowerPercent, upperPercent);
              }
          }
      }
  
      /**
       * Multiplies the range on the range axis/axes by the specified factor.
       *
       * @param factor  the zoom factor.
       * @param info  the plot rendering info.
       * @param source  the source point.
       *
       * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
       */
      @Override
      public void zoomRangeAxes(double factor, PlotRenderingInfo info,
                                Point2D source) {
          // delegate to other method
          zoomRangeAxes(factor, info, source, false);
      }
  
      /**
       * Multiplies the range on the range axis/axes by the specified factor.
       *
       * @param factor  the zoom factor.
       * @param info  the plot rendering info.
       * @param source  the source point.
       * @param useAnchor  a flag that controls whether or not the source point
       *         is used for the zoom anchor.
       *
       * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
       *
       * @since 1.0.7
       */
      @Override
      public void zoomRangeAxes(double factor, PlotRenderingInfo info,
                                Point2D source, boolean useAnchor) {
  
          // perform the zoom on each range axis
          for (int i = 0; i < this.rangeAxes.size(); i++) {
              ValueAxis rangeAxis = this.rangeAxes.get(i);
              if (rangeAxis != null) {
                  if (useAnchor) {
                      // get the relevant source coordinate given the plot
                      // orientation
                      double sourceY = source.getY();
                      if (this.orientation == PlotOrientation.HORIZONTAL) {
                          sourceY = source.getX();
                      }
                      double anchorY = rangeAxis.java2DToValue(sourceY,
                              info.getDataArea(), getRangeAxisEdge());
                      rangeAxis.resizeRange2(factor, anchorY);
                  }
                  else {
                      rangeAxis.resizeRange(factor);
                  }
              }
          }
      }
  
      /**
       * Zooms in on the range axes.
       *
       * @param lowerPercent  the lower bound.
       * @param upperPercent  the upper bound.
       * @param info  the plot rendering info.
       * @param source  the source point.
       *
       * @see #zoomDomainAxes(double, double, PlotRenderingInfo, Point2D)
       */
      @Override
      public void zoomRangeAxes(double lowerPercent, double upperPercent,
                                PlotRenderingInfo info, Point2D source) {
          for (int i = 0; i < this.rangeAxes.size(); i++) {
              ValueAxis rangeAxis = this.rangeAxes.get(i);
              if (rangeAxis != null) {
                  rangeAxis.zoomRange(lowerPercent, upperPercent);
              }
          }
      }
  
      /**
       * Returns <code>true</code>, indicating that the domain axis/axes for this
       * plot are zoomable.
       *
       * @return A boolean.
       *
       * @see #isRangeZoomable()
       */
      @Override
      public boolean isDomainZoomable() {
          return true;
      }
  
      /**
       * Returns <code>true</code>, indicating that the range axis/axes for this
       * plot are zoomable.
       *
       * @return A boolean.
       *
       * @see #isDomainZoomable()
       */
      @Override
      public boolean isRangeZoomable() {
          return true;
      }
  
      /**
       * Returns the number of series in the primary dataset for this plot.  If
       * the dataset is <code>null</code>, the method returns 0.
       *
       * @return The series count.
       */
      public int getSeriesCount() {
          int result = 0;
          XYDataset dataset = getDataset();
          if (dataset != null) {
              result = dataset.getSeriesCount();
          }
          return result;
      }
  
      /**
       * Returns the fixed legend items, if any.
       *
       * @return The legend items (possibly <code>null</code>).
       *
       * @see #setFixedLegendItems(LegendItemCollection)
       */
      public List<LegendItem> getFixedLegendItems() {
          return this.fixedLegendItems;
      }
  
      /**
       * Sets the fixed legend items for the plot.  Leave this set to
       * <code>null</code> if you prefer the legend items to be created
       * automatically.
       *
       * @param items  the legend items (<code>null</code> permitted).
       *
       * @see #getFixedLegendItems()
       */
      public void setFixedLegendItems(List<LegendItem> items) {
          this.fixedLegendItems = items;
          fireChangeEvent();
      }
  
      /**
       * Returns the legend items for the plot.  Each legend item is generated by
       * the plot's renderer, since the renderer is responsible for the visual
       * representation of the data.
       *
       * @return The legend items.
       */
      @Override
      public List<LegendItem> getLegendItems() {
          if (this.fixedLegendItems != null) {
              return this.fixedLegendItems;
          }
          List<LegendItem> result = new ArrayList<LegendItem>();
          int count = this.datasets.size();
          for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
              XYDataset dataset = getDataset(datasetIndex);
              if (dataset != null) {
                  XYItemRenderer renderer = getRenderer(datasetIndex);
                  if (renderer == null) {
                      renderer = getRenderer(0);
                  }
                  if (renderer != null) {
                      int seriesCount = dataset.getSeriesCount();
                      for (int i = 0; i < seriesCount; i++) {
                          if (renderer.isSeriesVisible(i)
                                  && renderer.isSeriesVisibleInLegend(i)) {
                              LegendItem item = renderer.getLegendItem(
                                      datasetIndex, i);
                              if (item != null) {
                                  result.add(item);
                              }
                          }
                      }
                  }
              }
          }
          return result;
      }
  
      /**
       * Tests this plot for equality with another object.
       *
       * @param obj  the object (<code>null</code> permitted).
       *
       * @return <code>true</code> or <code>false</code>.
       */
      @Override
      public boolean equals(Object obj) {
          if (obj == this) {
              return true;
          }
          if (!(obj instanceof XYPlot)) {
              return false;
          }
          XYPlot that = (XYPlot) obj;
          if (this.weight != that.weight) {
              return false;
          }
          if (this.orientation != that.orientation) {
              return false;
          }
          if (!this.domainAxes.equals(that.domainAxes)) {
              return false;
          }
          if (!this.domainAxisLocations.equals(that.domainAxisLocations)) {
              return false;
          }
          if (this.rangeCrosshairLockedOnData
                  != that.rangeCrosshairLockedOnData) {
              return false;
          }
          if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
              return false;
          }
          if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) {
              return false;
          }
          if (this.domainMinorGridlinesVisible
                  != that.domainMinorGridlinesVisible) {
              return false;
          }
          if (this.rangeMinorGridlinesVisible
                  != that.rangeMinorGridlinesVisible) {
              return false;
          }
          if (this.domainZeroBaselineVisible != that.domainZeroBaselineVisible) {
              return false;
          }
          if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) {
              return false;
          }
          if (this.domainCrosshairVisible != that.domainCrosshairVisible) {
              return false;
          }
          if (this.domainCrosshairValue != that.domainCrosshairValue) {
              return false;
          }
          if (this.domainCrosshairLockedOnData
                  != that.domainCrosshairLockedOnData) {
              return false;
          }
          if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) {
              return false;
          }
          if (this.rangeCrosshairValue != that.rangeCrosshairValue) {
              return false;
          }
          if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.renderers, that.renderers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.rangeAxes, that.rangeAxes)) {
              return false;
          }
          if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.datasetToDomainAxesMap,
                  that.datasetToDomainAxesMap)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.datasetToRangeAxesMap,
                  that.datasetToRangeAxesMap)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.domainGridlineStroke,
                  that.domainGridlineStroke)) {
              return false;
          }
          if (!PaintUtilities.equal(this.domainGridlinePaint,
                  that.domainGridlinePaint)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.rangeGridlineStroke,
                  that.rangeGridlineStroke)) {
              return false;
          }
          if (!PaintUtilities.equal(this.rangeGridlinePaint,
                  that.rangeGridlinePaint)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.domainMinorGridlineStroke,
                  that.domainMinorGridlineStroke)) {
              return false;
          }
          if (!PaintUtilities.equal(this.domainMinorGridlinePaint,
                  that.domainMinorGridlinePaint)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.rangeMinorGridlineStroke,
                  that.rangeMinorGridlineStroke)) {
              return false;
          }
          if (!PaintUtilities.equal(this.rangeMinorGridlinePaint,
                  that.rangeMinorGridlinePaint)) {
              return false;
          }
          if (!PaintUtilities.equal(this.domainZeroBaselinePaint,
                  that.domainZeroBaselinePaint)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.domainZeroBaselineStroke,
                  that.domainZeroBaselineStroke)) {
              return false;
          }
          if (!PaintUtilities.equal(this.rangeZeroBaselinePaint,
                  that.rangeZeroBaselinePaint)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.rangeZeroBaselineStroke,
                  that.rangeZeroBaselineStroke)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.domainCrosshairStroke,
                  that.domainCrosshairStroke)) {
              return false;
          }
          if (!PaintUtilities.equal(this.domainCrosshairPaint,
                  that.domainCrosshairPaint)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.rangeCrosshairStroke,
                  that.rangeCrosshairStroke)) {
              return false;
          }
          if (!PaintUtilities.equal(this.rangeCrosshairPaint,
                  that.rangeCrosshairPaint)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
                  that.foregroundDomainMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
                  that.backgroundDomainMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
                  that.foregroundRangeMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
                  that.backgroundRangeMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
                  that.foregroundDomainMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
                  that.backgroundDomainMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
                  that.foregroundRangeMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
                  that.backgroundRangeMarkers)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.annotations, that.annotations)) {
              return false;
          }
          if (!ObjectUtilities.equal(this.fixedLegendItems,
                  that.fixedLegendItems)) {
              return false;
          }
          if (!PaintUtilities.equal(this.domainTickBandPaint,
                  that.domainTickBandPaint)) {
              return false;
          }
          if (!PaintUtilities.equal(this.rangeTickBandPaint,
                  that.rangeTickBandPaint)) {
              return false;
          }
          if (!this.quadrantOrigin.equals(that.quadrantOrigin)) {
              return false;
          }
          for (int i = 0; i < 4; i++) {
              if (!PaintUtilities.equal(this.quadrantPaint[i],
                      that.quadrantPaint[i])) {
                  return false;
              }
          }
          if (!ObjectUtilities.equal(this.shadowGenerator,
                  that.shadowGenerator)) {
              return false;
          }
          return super.equals(obj);
      }
  
  
      /**
       * Returns dLeft/dRight value of selection interval (timestamp)
       *
       * @param rect  the selection rectangle
       * @param info  the plot rendering info.
       * @param select  0 - dLeft, 1 - dRight
       * @return A double.
       *
       */
      public double getJava2dToValue(Rectangle2D rect,
                                     PlotRenderingInfo info,
                                     int select) {
          double temp = 0;
          for (int i = 0; i < this.domainAxes.size(); i++) {
              ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
              temp = domainAxis.java2DToValue(
                      (select == 0 ? rect.getMinX() : rect.getMaxX()),
                      info.getDataArea(),
                      getDomainAxisEdge());
          }
          return temp;
      }
  
      /**
       * Returns dLeft/dRight value of selection interval (in Java2D)
       *
       * @param dValue  the selection rectangle
       * @param info  the plot rendering info.
       * @return A double.
       *
       */
      public double getValueToJava2d(double dValue,
                                     PlotRenderingInfo info) {
          double temp = 0;
          for (int i = 0; i < this.domainAxes.size(); i++) {
              ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
              temp = domainAxis.valueToJava2D(
                      dValue,
                      info.getDataArea(),
                      getDomainAxisEdge());
          }
          return temp;
      }
  
      /**
       * Returns a clone of the plot.
       *
       * @return A clone.
       *
       * @throws CloneNotSupportedException  this can occur if some component of
       *         the plot cannot be cloned.
       */
      @Override
      public Object clone() throws CloneNotSupportedException {
  
          XYPlot clone = (XYPlot) super.clone();
          clone.domainAxes = ObjectUtilities.clone(this.domainAxes);
          for (int i = 0; i < this.domainAxes.size(); i++) {
              ValueAxis axis = this.domainAxes.get(i);
              if (axis != null) {
                  ValueAxis clonedAxis = (ValueAxis) axis.clone();
                  clone.domainAxes.set(i, clonedAxis);
                  clonedAxis.setPlot(clone);
                  clonedAxis.addChangeListener(clone);
              }
          }
          clone.domainAxisLocations = (ObjectList<AxisLocation>)
                  this.domainAxisLocations.clone();
  
          clone.rangeAxes = ObjectUtilities.clone(this.rangeAxes);
          for (int i = 0; i < this.rangeAxes.size(); i++) {
              ValueAxis axis = this.rangeAxes.get(i);
              if (axis != null) {
                  ValueAxis clonedAxis = (ValueAxis) axis.clone();
                  clone.rangeAxes.set(i, clonedAxis);
                  clonedAxis.setPlot(clone);
                  clonedAxis.addChangeListener(clone);
              }
          }
          clone.rangeAxisLocations = ObjectUtilities.clone(
                  this.rangeAxisLocations);
  
          // the datasets are not cloned, but listeners need to be added...
          clone.datasets = ObjectUtilities.clone(this.datasets);
          for (int i = 0; i < clone.datasets.size(); ++i) {
              XYDataset d = getDataset(i);
              if (d != null) {
                  d.addChangeListener(clone);
              }
          }
  
          clone.datasetToDomainAxesMap = new TreeMap<Integer, List<Integer>>();
          clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap);
          clone.datasetToRangeAxesMap = new TreeMap<Integer, List<Integer>>();
          clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap);
  
          clone.renderers = ObjectUtilities.clone(this.renderers);
          for (int i = 0; i < this.renderers.size(); i++) {
              XYItemRenderer renderer2 = this.renderers.get(i);
              if (renderer2 instanceof PublicCloneable) {
                  PublicCloneable pc = (PublicCloneable) renderer2;
                  XYItemRenderer rc = (XYItemRenderer) pc.clone();
                  clone.renderers.set(i, rc);
                  rc.setPlot(clone);
                  rc.addChangeListener(clone);
              }
          }
          clone.foregroundDomainMarkers = ObjectUtilities.clone(
                  this.foregroundDomainMarkers);
          clone.backgroundDomainMarkers = ObjectUtilities.clone(
                  this.backgroundDomainMarkers);
          clone.foregroundRangeMarkers = ObjectUtilities.clone(
                  this.foregroundRangeMarkers);
          clone.backgroundRangeMarkers = ObjectUtilities.clone(
                  this.backgroundRangeMarkers);
          clone.annotations = ObjectUtilities.deepClone(this.annotations);
          if (this.fixedDomainAxisSpace != null) {
              clone.fixedDomainAxisSpace = ObjectUtilities.clone(
                      this.fixedDomainAxisSpace);
          }
          if (this.fixedRangeAxisSpace != null) {
              clone.fixedRangeAxisSpace = ObjectUtilities.clone(
                      this.fixedRangeAxisSpace);
          }
          if (this.fixedLegendItems != null) {
              clone.fixedLegendItems
                      = ObjectUtilities.deepClone(this.fixedLegendItems);
          }
          clone.quadrantOrigin = ObjectUtilities.clone(
                  this.quadrantOrigin);
          clone.quadrantPaint = this.quadrantPaint.clone();
          return clone;
  
      }
  
      /**
       * Provides serialization support.
       *
       * @param stream  the output stream.
       *
       * @throws IOException  if there is an I/O error.
       */
      private void writeObject(ObjectOutputStream stream) throws IOException {
          stream.defaultWriteObject();
          SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
          SerialUtilities.writePaint(this.domainGridlinePaint, stream);
          SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
          SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
          SerialUtilities.writeStroke(this.domainMinorGridlineStroke, stream);
          SerialUtilities.writePaint(this.domainMinorGridlinePaint, stream);
          SerialUtilities.writeStroke(this.rangeMinorGridlineStroke, stream);
          SerialUtilities.writePaint(this.rangeMinorGridlinePaint, stream);
          SerialUtilities.writeStroke(this.rangeZeroBaselineStroke, stream);
          SerialUtilities.writePaint(this.rangeZeroBaselinePaint, stream);
          SerialUtilities.writeStroke(this.domainCrosshairStroke, stream);
          SerialUtilities.writePaint(this.domainCrosshairPaint, stream);
          SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream);
          SerialUtilities.writePaint(this.rangeCrosshairPaint, stream);
          SerialUtilities.writePaint(this.domainTickBandPaint, stream);
          SerialUtilities.writePaint(this.rangeTickBandPaint, stream);
          SerialUtilities.writePoint2D(this.quadrantOrigin, stream);
          for (int i = 0; i < 4; i++) {
              SerialUtilities.writePaint(this.quadrantPaint[i], stream);
          }
          SerialUtilities.writeStroke(this.domainZeroBaselineStroke, stream);
          SerialUtilities.writePaint(this.domainZeroBaselinePaint, stream);
      }
  
      /**
       * Provides serialization support.
       *
       * @param stream  the input stream.
       *
       * @throws IOException  if there is an I/O error.
       * @throws ClassNotFoundException  if there is a classpath problem.
       */
      private void readObject(ObjectInputStream stream)
          throws IOException, ClassNotFoundException {
  
          stream.defaultReadObject();
          this.domainGridlineStroke = SerialUtilities.readStroke(stream);
          this.domainGridlinePaint = SerialUtilities.readPaint(stream);
          this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
          this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
          this.domainMinorGridlineStroke = SerialUtilities.readStroke(stream);
          this.domainMinorGridlinePaint = SerialUtilities.readPaint(stream);
          this.rangeMinorGridlineStroke = SerialUtilities.readStroke(stream);
          this.rangeMinorGridlinePaint = SerialUtilities.readPaint(stream);
          this.rangeZeroBaselineStroke = SerialUtilities.readStroke(stream);
          this.rangeZeroBaselinePaint = SerialUtilities.readPaint(stream);
          this.domainCrosshairStroke = SerialUtilities.readStroke(stream);
          this.domainCrosshairPaint = SerialUtilities.readPaint(stream);
          this.rangeCrosshairStroke = SerialUtilities.readStroke(stream);
          this.rangeCrosshairPaint = SerialUtilities.readPaint(stream);
          this.domainTickBandPaint = SerialUtilities.readPaint(stream);
          this.rangeTickBandPaint = SerialUtilities.readPaint(stream);
          this.quadrantOrigin = SerialUtilities.readPoint2D(stream);
          this.quadrantPaint = new Paint[4];
          for (int i = 0; i < 4; i++) {
              this.quadrantPaint[i] = SerialUtilities.readPaint(stream);
          }
  
          this.domainZeroBaselineStroke = SerialUtilities.readStroke(stream);
          this.domainZeroBaselinePaint = SerialUtilities.readPaint(stream);
  
          // register the plot as a listener with its axes, datasets, and
          // renderers...
          int domainAxisCount = this.domainAxes.size();
          for (int i = 0; i < domainAxisCount; i++) {
              Axis axis = this.domainAxes.get(i);
              if (axis != null) {
                  axis.setPlot(this);
                  axis.addChangeListener(this);
              }
          }
          int rangeAxisCount = this.rangeAxes.size();
          for (int i = 0; i < rangeAxisCount; i++) {
              Axis axis = this.rangeAxes.get(i);
              if (axis != null) {
                  axis.setPlot(this);
                  axis.addChangeListener(this);
              }
          }
          int datasetCount = this.datasets.size();
          for (int i = 0; i < datasetCount; i++) {
              Dataset dataset = this.datasets.get(i);
              if (dataset != null) {
                  dataset.addChangeListener(this);
              }
          }
          int rendererCount = this.renderers.size();
          for (int i = 0; i < rendererCount; i++) {
              XYItemRenderer renderer = this.renderers.get(i);
              if (renderer != null) {
                  renderer.addChangeListener(this);
              }
          }
  
      }
  
  }
  