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