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