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