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