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