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