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