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