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