    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025     * Other names may be trademarks of their respective owners.]
    026     *
    027     * -----------------
    028     * CategoryPlot.java
    029     * -----------------
    030     * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Jeremy Bowman;
    034     *                   Arnaud Lelievre;
    035     *                   Richard West, Advanced Micro Devices, Inc.;
    036     *                   Ulrich Voigt - patch 2686040;
    037     *                   Peter Kolb - patches 2603321 and 2809117;
    038     *
    039     * Changes
    040     * -------
    041     * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
    042     * 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG);
    043     * 18-Sep-2001 : Updated header (DG);
    044     * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG);
    045     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
    046     * 23-Oct-2001 : Changed intro and trail gaps on bar plots to use percentage of
    047     *               available space rather than a fixed number of units (DG);
    048     * 12-Dec-2001 : Changed constructors to protected (DG);
    049     * 13-Dec-2001 : Added tooltips (DG);
    050     * 16-Jan-2002 : Increased maximum intro and trail gap percents, plus added
    051     *               some argument checking code.  Thanks to Taoufik Romdhane for
    052     *               suggesting this (DG);
    053     * 05-Feb-2002 : Added accessor methods for the tooltip generator, incorporated
    054     *               alpha-transparency for Plot and subclasses (DG);
    055     * 06-Mar-2002 : Updated import statements (DG);
    056     * 14-Mar-2002 : Renamed BarPlot.java --> CategoryPlot.java, and changed code
    057     *               to use the CategoryItemRenderer interface (DG);
    058     * 22-Mar-2002 : Dropped the getCategories() method (DG);
    059     * 23-Apr-2002 : Moved the dataset from the JFreeChart class to the Plot
    060     *               class (DG);
    061     * 29-Apr-2002 : New methods to support printing values at the end of bars,
    062     *               contributed by Jeremy Bowman (DG);
    063     * 11-May-2002 : New methods for label visibility and overlaid plot support,
    064     *               contributed by Jeremy Bowman (DG);
    065     * 06-Jun-2002 : Removed the tooltip generator, this is now stored with the
    066     *               renderer.  Moved constants into the CategoryPlotConstants
    067     *               interface.  Updated Javadoc comments (DG);
    068     * 10-Jun-2002 : Overridden datasetChanged() method to update the upper and
    069     *               lower bound on the range axis (if necessary), updated
    070     *               Javadocs (DG);
    071     * 25-Jun-2002 : Removed redundant imports (DG);
    072     * 20-Aug-2002 : Changed the constructor for Marker (DG);
    073     * 28-Aug-2002 : Added listener notification to setDomainAxis() and
    074     *               setRangeAxis() (DG);
    075     * 23-Sep-2002 : Added getLegendItems() method and fixed errors reported by
    076     *               Checkstyle (DG);
    077     * 28-Oct-2002 : Changes to the CategoryDataset interface (DG);
    078     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
    079     * 07-Nov-2002 : Renamed labelXXX as valueLabelXXX (DG);
    080     * 18-Nov-2002 : Added grid settings for both domain and range axis (previously
    081     *               these were set in the axes) (DG);
    082     * 19-Nov-2002 : Added axis location parameters to constructor (DG);
    083     * 17-Jan-2003 : Moved to com.jrefinery.chart.plot package (DG);
    084     * 14-Feb-2003 : Fixed bug in auto-range calculation for secondary axis (DG);
    085     * 26-Mar-2003 : Implemented Serializable (DG);
    086     * 02-May-2003 : Moved render() method up from subclasses. Added secondary
    087     *               range markers. Added an attribute to control the dataset
    088     *               rendering order.  Added a drawAnnotations() method.  Changed
    089     *               the axis location from an int to an AxisLocation (DG);
    090     * 07-May-2003 : Merged HorizontalCategoryPlot and VerticalCategoryPlot into
    091     *               this class (DG);
    092     * 02-Jun-2003 : Removed check for range axis compatibility (DG);
    093     * 04-Jul-2003 : Added a domain gridline position attribute (DG);
    094     * 21-Jul-2003 : Moved DrawingSupplier to Plot superclass (DG);
    095     * 19-Aug-2003 : Added equals() method and implemented Cloneable (DG);
    096     * 01-Sep-2003 : Fixed bug 797466 (no change event when secondary dataset
    097     *               changes) (DG);
    098     * 02-Sep-2003 : Fixed bug 795209 (wrong dataset checked in render2 method) and
    099     *               790407 (initialise method) (DG);
    100     * 08-Sep-2003 : Added internationalization via use of properties
    101     *               resourceBundle (RFE 690236) (AL);
    102     * 08-Sep-2003 : Fixed bug (wrong secondary range axis being used).  Changed
    103     *               ValueAxis API (DG);
    104     * 10-Sep-2003 : Fixed bug in setRangeAxis() method (DG);
    105     * 15-Sep-2003 : Fixed two bugs in serialization, implemented
    106     *               PublicCloneable (DG);
    107     * 23-Oct-2003 : Added event notification for changes to renderer (DG);
    108     * 26-Nov-2003 : Fixed bug (849645) in clearRangeMarkers() method (DG);
    109     * 03-Dec-2003 : Modified draw method to accept anchor (DG);
    110     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
    111     * 10-Mar-2004 : Fixed bug in axis range calculation when secondary renderer is
    112     *               stacked (DG);
    113     * 12-May-2004 : Added fixed legend items (DG);
    114     * 19-May-2004 : Added check for null legend item from renderer (DG);
    115     * 02-Jun-2004 : Updated the DatasetRenderingOrder class (DG);
    116     * 05-Nov-2004 : Renamed getDatasetsMappedToRangeAxis()
    117     *               --> datasetsMappedToRangeAxis(), and ensured that returned
    118     *               list doesn't contain null datasets (DG);
    119     * 12-Nov-2004 : Implemented new Zoomable interface (DG);
    120     * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() in
    121     *               CategoryItemRenderer (DG);
    122     * 04-May-2005 : Fixed serialization of range markers (DG);
    123     * 05-May-2005 : Updated draw() method parameters (DG);
    124     * 20-May-2005 : Added setDomainAxes() and setRangeAxes() methods, as per
    125     *               RFE 1183100 (DG);
    126     * 01-Jun-2005 : Upon deserialization, register plot as a listener with its
    127     *               axes, dataset(s) and renderer(s) - see patch 1209475 (DG);
    128     * 02-Jun-2005 : Added support for domain markers (DG);
    129     * 06-Jun-2005 : Fixed equals() method for use with GradientPaint (DG);
    130     * 09-Jun-2005 : Added setRenderers(), as per RFE 1183100 (DG);
    131     * 16-Jun-2005 : Added getDomainAxisCount() and getRangeAxisCount() methods, to
    132     *               match XYPlot (see RFE 1220495) (DG);
    133     * ------------- JFREECHART 1.0.x ---------------------------------------------
    134     * 11-Jan-2006 : Added configureRangeAxes() to rendererChanged(), since the
    135     *               renderer might influence the axis range (DG);
    136     * 27-Jan-2006 : Added various null argument checks (DG);
    137     * 18-Aug-2006 : Added getDatasetCount() method, plus a fix for bug drawing
    138     *               category labels, thanks to Adriaan Joubert (1277726) (DG);
    139     * 05-Sep-2006 : Added MarkerChangeEvent support (DG);
    140     * 30-Oct-2006 : Added getDomainAxisIndex(), datasetsMappedToDomainAxis() and
    141     *               getCategoriesForAxis() methods (DG);
    142     * 22-Nov-2006 : Fire PlotChangeEvent from setColumnRenderingOrder() and
    143     *               setRowRenderingOrder() (DG);
    144     * 29-Nov-2006 : Fix for bug 1605207 (IntervalMarker exceeds bounds of data
    145     *               area) (DG);
    146     * 26-Feb-2007 : Fix for bug 1669218 (setDomainAxisLocation() notify argument
    147     *               ignored) (DG);
    148     * 13-Mar-2007 : Added null argument checks for setRangeCrosshairPaint() and
    149     *               setRangeCrosshairStroke(), fixed clipping for
    150     *               annotations (DG);
    151     * 07-Jun-2007 : Override drawBackground() for new GradientPaint handling (DG);
    152     * 10-Jul-2007 : Added getRangeAxisIndex(ValueAxis) method (DG);
    153     * 24-Sep-2007 : Implemented new zoom methods (DG);
    154     * 25-Oct-2007 : Added some argument checks (DG);
    155     * 05-Nov-2007 : Applied patch 1823697, by Richard West, for removal of domain
    156     *               and range markers (DG);
    157     * 14-Nov-2007 : Added missing event notifications (DG);
    158     * 25-Mar-2008 : Added new methods with optional notification - see patch
    159     *               1913751 (DG);
    160     * 07-Apr-2008 : Fixed NPE in removeDomainMarker() and
    161     *               removeRangeMarker() (DG);
    162     * 23-Apr-2008 : Fixed equals() and clone() methods (DG);
    163     * 26-Jun-2008 : Fixed crosshair support (DG);
    164     * 10-Jul-2008 : Fixed outline visibility for 3D renderers (DG);
    165     * 12-Aug-2008 : Added rendererCount() method (DG);
    166     * 25-Nov-2008 : Added facility to map datasets to multiples axes (DG);
    167     * 15-Dec-2008 : Cleaned up grid drawing methods (DG);
    168     * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
    169     *               Jess Thrysoee (DG);
    170     * 21-Jan-2009 : Added rangeMinorGridlinesVisible flag (DG);
    171     * 18-Mar-2009 : Modified anchored zoom behaviour (DG);
    172     * 19-Mar-2009 : Implemented Pannable interface - see patch 2686040 (DG);
    173     * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG);
    174     * 24-Jun-2009 : Implemented AnnotationChangeListener (see patch 2809117 by
    175     *               PK) (DG);
    176     * 06-Jul-2009 : Fix for cloning of renderers - see bug 2817504 (DG)
    177     * 10-Jul-2009 : Added optional drop shadow generator (DG);
    178     * 27-Sep-2011 : Fixed annotation import (DG);
    179     * 18-Oct-2011 : Fixed tooltip offset with shadow generator (DG);
    180     * 20-Nov-2011 : Initialise shadow generator as null (DG);
    181     *
    182     */
    183
    184    package org.jfree.chart.plot;
    185
    186    import java.awt.AlphaComposite;
    187    import java.awt.BasicStroke;
    188    import java.awt.Color;
    189    import java.awt.Composite;
    190    import java.awt.Font;
    191    import java.awt.Graphics2D;
    192    import java.awt.Paint;
    193    import java.awt.Rectangle;
    194    import java.awt.Shape;
    195    import java.awt.Stroke;
    196    import java.awt.geom.Line2D;
    197    import java.awt.geom.Point2D;
    198    import java.awt.geom.Rectangle2D;
    199    import java.awt.image.BufferedImage;
    200    import java.io.IOException;
    201    import java.io.ObjectInputStream;
    202    import java.io.ObjectOutputStream;
    203    import java.io.Serializable;
    204    import java.util.ArrayList;
    205    import java.util.Collection;
    206    import java.util.Collections;
    207    import java.util.HashMap;
    208    import java.util.HashSet;
    209    import java.util.Iterator;
    210    import java.util.List;
    211    import java.util.Map;
    212    import java.util.ResourceBundle;
    213    import java.util.Set;
    214    import java.util.TreeMap;
    215    import org.jfree.chart.LegendItemCollection;
    216    import org.jfree.chart.annotations.Annotation;
    217    import org.jfree.chart.annotations.CategoryAnnotation;
    218    import org.jfree.chart.axis.Axis;
    219    import org.jfree.chart.axis.AxisCollection;
    220    import org.jfree.chart.axis.AxisLocation;
    221    import org.jfree.chart.axis.AxisSpace;
    222    import org.jfree.chart.axis.AxisState;
    223    import org.jfree.chart.axis.CategoryAnchor;
    224    import org.jfree.chart.axis.CategoryAxis;
    225    import org.jfree.chart.axis.TickType;
    226    import org.jfree.chart.axis.ValueAxis;
    227    import org.jfree.chart.axis.ValueTick;
    228    import org.jfree.chart.event.AnnotationChangeEvent;
    229    import org.jfree.chart.event.AnnotationChangeListener;
    230    import org.jfree.chart.event.ChartChangeEventType;
    231    import org.jfree.chart.event.PlotChangeEvent;
    232    import org.jfree.chart.event.RendererChangeEvent;
    233    import org.jfree.chart.event.RendererChangeListener;
    234    import org.jfree.chart.renderer.category.AbstractCategoryItemRenderer;
    235    import org.jfree.chart.renderer.category.CategoryItemRenderer;
    236    import org.jfree.chart.renderer.category.CategoryItemRendererState;
    237    import org.jfree.chart.util.ResourceBundleWrapper;
    238    import org.jfree.chart.util.ShadowGenerator;
    239    import org.jfree.data.Range;
    240    import org.jfree.data.category.CategoryDataset;
    241    import org.jfree.data.general.Dataset;
    242    import org.jfree.data.general.DatasetChangeEvent;
    243    import org.jfree.data.general.DatasetUtilities;
    244    import org.jfree.io.SerialUtilities;
    245    import org.jfree.ui.Layer;
    246    import org.jfree.ui.RectangleEdge;
    247    import org.jfree.ui.RectangleInsets;
    248    import org.jfree.util.ObjectList;
    249    import org.jfree.util.ObjectUtilities;
    250    import org.jfree.util.PaintUtilities;
    251    import org.jfree.util.PublicCloneable;
    252    import org.jfree.util.ShapeUtilities;
    253    import org.jfree.util.SortOrder;
    254
    255    /**
    256     * A general plotting class that uses data from a {@link CategoryDataset} and
    257     * renders each data item using a {@link CategoryItemRenderer}.
    258     */
    259    public class CategoryPlot extends Plot implements ValueAxisPlot, Pannable,
    260            Zoomable, AnnotationChangeListener, RendererChangeListener,
    261            Cloneable, PublicCloneable, Serializable {
    262
    263        /** For serialization. */
    264        private static final long serialVersionUID = -3537691700434728188L;
    265
    266        /**
    267         * The default visibility of the grid lines plotted against the domain
    268         * axis.
    269         */
    270        public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false;
    271
    272        /**
    273         * The default visibility of the grid lines plotted against the range
    274         * axis.
    275         */
    276        public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true;
    277
    278        /** The default grid line stroke. */
    279        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
    280                BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[]
    281                {2.0f, 2.0f}, 0.0f);
    282
    283        /** The default grid line paint. */
    284        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
    285
    286        /** The default value label font. */
    287        public static final Font DEFAULT_VALUE_LABEL_FONT = new Font("SansSerif",
    288                Font.PLAIN, 10);
    289
    290        /**
    291         * The default crosshair visibility.
    292         *
    293         * @since 1.0.5
    294         */
    295        public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
    296
    297        /**
    298         * The default crosshair stroke.
    299         *
    300         * @since 1.0.5
    301         */
    302        public static final Stroke DEFAULT_CROSSHAIR_STROKE
    303                = DEFAULT_GRIDLINE_STROKE;
    304
    305        /**
    306         * The default crosshair paint.
    307         *
    308         * @since 1.0.5
    309         */
    310        public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
    311
    312        /** The resourceBundle for the localization. */
    313        protected static ResourceBundle localizationResources
    314                = ResourceBundleWrapper.getBundle(
    315                "org.jfree.chart.plot.LocalizationBundle");
    316
    317        /** The plot orientation. */
    318        private PlotOrientation orientation;
    319
    320        /** The offset between the data area and the axes. */
    321        private RectangleInsets axisOffset;
    322
    323        /** Storage for the domain axes. */
    324        private ObjectList domainAxes;
    325
    326        /** Storage for the domain axis locations. */
    327        private ObjectList domainAxisLocations;
    328
    329        /**
    330         * A flag that controls whether or not the shared domain axis is drawn
    331         * (only relevant when the plot is being used as a subplot).
    332         */
    333        private boolean drawSharedDomainAxis;
    334
    335        /** Storage for the range axes. */
    336        private ObjectList rangeAxes;
    337
    338        /** Storage for the range axis locations. */
    339        private ObjectList rangeAxisLocations;
    340
    341        /** Storage for the datasets. */
    342        private ObjectList datasets;
    343
    344        /** Storage for keys that map datasets to domain axes. */
    345        private TreeMap datasetToDomainAxesMap;
    346
    347        /** Storage for keys that map datasets to range axes. */
    348        private TreeMap datasetToRangeAxesMap;
    349
    350        /** Storage for the renderers. */
    351        private ObjectList renderers;
    352
    353        /** The dataset rendering order. */
    354        private DatasetRenderingOrder renderingOrder
    355                = DatasetRenderingOrder.REVERSE;
    356
    357        /**
    358         * Controls the order in which the columns are traversed when rendering the
    359         * data items.
    360         */
    361        private SortOrder columnRenderingOrder = SortOrder.ASCENDING;
    362
    363        /**
    364         * Controls the order in which the rows are traversed when rendering the
    365         * data items.
    366         */
    367        private SortOrder rowRenderingOrder = SortOrder.ASCENDING;
    368
    369        /**
    370         * A flag that controls whether the grid-lines for the domain axis are
    371         * visible.
    372         */
    373        private boolean domainGridlinesVisible;
    374
    375        /** The position of the domain gridlines relative to the category. */
    376        private CategoryAnchor domainGridlinePosition;
    377
    378        /** The stroke used to draw the domain grid-lines. */
    379        private transient Stroke domainGridlineStroke;
    380
    381        /** The paint used to draw the domain  grid-lines. */
    382        private transient Paint domainGridlinePaint;
    383
    384        /**
    385         * A flag that controls whether or not the zero baseline against the range
    386         * axis is visible.
    387         *
    388         * @since 1.0.13
    389         */
    390        private boolean rangeZeroBaselineVisible;
    391
    392        /**
    393         * The stroke used for the zero baseline against the range axis.
    394         *
    395         * @since 1.0.13
    396         */
    397        private transient Stroke rangeZeroBaselineStroke;
    398
    399        /**
    400         * The paint used for the zero baseline against the range axis.
    401         *
    402         * @since 1.0.13
    403         */
    404        private transient Paint rangeZeroBaselinePaint;
    405
    406        /**
    407         * A flag that controls whether the grid-lines for the range axis are
    408         * visible.
    409         */
    410        private boolean rangeGridlinesVisible;
    411
    412        /** The stroke used to draw the range axis grid-lines. */
    413        private transient Stroke rangeGridlineStroke;
    414
    415        /** The paint used to draw the range axis grid-lines. */
    416        private transient Paint rangeGridlinePaint;
    417
    418        /**
    419         * A flag that controls whether or not gridlines are shown for the minor
    420         * tick values on the primary range axis.
    421         *
    422         * @since 1.0.13
    423         */
    424        private boolean rangeMinorGridlinesVisible;
    425
    426        /**
    427         * The stroke used to draw the range minor grid-lines.
    428         *
    429         * @since 1.0.13
    430         */
    431        private transient Stroke rangeMinorGridlineStroke;
    432
    433        /**
    434         * The paint used to draw the range minor grid-lines.
    435         *
    436         * @since 1.0.13
    437         */
    438        private transient Paint rangeMinorGridlinePaint;
    439
    440        /** The anchor value. */
    441        private double anchorValue;
    442
    443        /**
    444         * The index for the dataset that the crosshairs are linked to (this
    445         * determines which axes the crosshairs are plotted against).
    446         *
    447         * @since 1.0.11
    448         */
    449        private int crosshairDatasetIndex;
    450
    451        /**
    452         * A flag that controls the visibility of the domain crosshair.
    453         *
    454         * @since 1.0.11
    455         */
    456        private boolean domainCrosshairVisible;
    457
    458        /**
    459         * The row key for the crosshair point.
    460         *
    461         * @since 1.0.11
    462         */
    463        private Comparable domainCrosshairRowKey;
    464
    465        /**
    466         * The column key for the crosshair point.
    467         *
    468         * @since 1.0.11
    469         */
    470        private Comparable domainCrosshairColumnKey;
    471
    472        /**
    473         * The stroke used to draw the domain crosshair if it is visible.
    474         *
    475         * @since 1.0.11
    476         */
    477        private transient Stroke domainCrosshairStroke;
    478
    479        /**
    480         * The paint used to draw the domain crosshair if it is visible.
    481         *
    482         * @since 1.0.11
    483         */
    484        private transient Paint domainCrosshairPaint;
    485
    486        /** A flag that controls whether or not a range crosshair is drawn. */
    487        private boolean rangeCrosshairVisible;
    488
    489        /** The range crosshair value. */
    490        private double rangeCrosshairValue;
    491
    492        /** The pen/brush used to draw the crosshair (if any). */
    493        private transient Stroke rangeCrosshairStroke;
    494
    495        /** The color used to draw the crosshair (if any). */
    496        private transient Paint rangeCrosshairPaint;
    497
    498        /**
    499         * A flag that controls whether or not the crosshair locks onto actual
    500         * data points.
    501         */
    502        private boolean rangeCrosshairLockedOnData = true;
    503
    504        /** A map containing lists of markers for the domain axes. */
    505        private Map foregroundDomainMarkers;
    506
    507        /** A map containing lists of markers for the domain axes. */
    508        private Map backgroundDomainMarkers;
    509
    510        /** A map containing lists of markers for the range axes. */
    511        private Map foregroundRangeMarkers;
    512
    513        /** A map containing lists of markers for the range axes. */
    514        private Map backgroundRangeMarkers;
    515
    516        /**
    517         * A (possibly empty) list of annotations for the plot.  The list should
    518         * be initialised in the constructor and never allowed to be
    519         * <code>null</code>.
    520         */
    521        private List annotations;
    522
    523        /**
    524         * The weight for the plot (only relevant when the plot is used as a subplot
    525         * within a combined plot).
    526         */
    527        private int weight;
    528
    529        /** The fixed space for the domain axis. */
    530        private AxisSpace fixedDomainAxisSpace;
    531
    532        /** The fixed space for the range axis. */
    533        private AxisSpace fixedRangeAxisSpace;
    534
    535        /**
    536         * An optional collection of legend items that can be returned by the
    537         * getLegendItems() method.
    538         */
    539        private LegendItemCollection fixedLegendItems;
    540
    541        /**
    542         * A flag that controls whether or not panning is enabled for the
    543         * range axis/axes.
    544         *
    545         * @since 1.0.13
    546         */
    547        private boolean rangePannable;
    548
    549        /**
    550         * The shadow generator for the plot (<code>null</code> permitted).
    551         *
    552         * @since 1.0.14
    553         */
    554        private ShadowGenerator shadowGenerator;
    555
    556        /**
    557         * Default constructor.
    558         */
    559        public CategoryPlot() {
    560            this(null, null, null, null);
    561        }
    562
    563        /**
    564         * Creates a new plot.
    565         *
    566         * @param dataset  the dataset (<code>null</code> permitted).
    567         * @param domainAxis  the domain axis (<code>null</code> permitted).
    568         * @param rangeAxis  the range axis (<code>null</code> permitted).
    569         * @param renderer  the item renderer (<code>null</code> permitted).
    570         *
    571         */
    572        public CategoryPlot(CategoryDataset dataset,
    573                            CategoryAxis domainAxis,
    574                            ValueAxis rangeAxis,
    575                            CategoryItemRenderer renderer) {
    576
    577            super();
    578
    579            this.orientation = PlotOrientation.VERTICAL;
    580
    581            // allocate storage for dataset, axes and renderers
    582            this.domainAxes = new ObjectList();
    583            this.domainAxisLocations = new ObjectList();
    584            this.rangeAxes = new ObjectList();
    585            this.rangeAxisLocations = new ObjectList();
    586
    587            this.datasetToDomainAxesMap = new TreeMap();
    588            this.datasetToRangeAxesMap = new TreeMap();
    589
    590            this.renderers = new ObjectList();
    591
    592            this.datasets = new ObjectList();
    593            this.datasets.set(0, dataset);
    594            if (dataset != null) {
    595                dataset.addChangeListener(this);
    596            }
    597
    598            this.axisOffset = RectangleInsets.ZERO_INSETS;
    599
    600            setDomainAxisLocation(AxisLocation.BOTTOM_OR_LEFT, false);
    601            setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false);
    602
    603            this.renderers.set(0, renderer);
    604            if (renderer != null) {
    605                renderer.setPlot(this);
    606                renderer.addChangeListener(this);
    607            }
    608
    609            this.domainAxes.set(0, domainAxis);
    610            this.mapDatasetToDomainAxis(0, 0);
    611            if (domainAxis != null) {
    612                domainAxis.setPlot(this);
    613                domainAxis.addChangeListener(this);
    614            }
    615            this.drawSharedDomainAxis = false;
    616
    617            this.rangeAxes.set(0, rangeAxis);
    618            this.mapDatasetToRangeAxis(0, 0);
    619            if (rangeAxis != null) {
    620                rangeAxis.setPlot(this);
    621                rangeAxis.addChangeListener(this);
    622            }
    623
    624            configureDomainAxes();
    625            configureRangeAxes();
    626
    627            this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE;
    628            this.domainGridlinePosition = CategoryAnchor.MIDDLE;
    629            this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
    630            this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
    631
    632            this.rangeZeroBaselineVisible = false;
    633            this.rangeZeroBaselinePaint = Color.black;
    634            this.rangeZeroBaselineStroke = new BasicStroke(0.5f);
    635
    636            this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE;
    637            this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
    638            this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
    639
    640            this.rangeMinorGridlinesVisible = false;
    641            this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE;
    642            this.rangeMinorGridlinePaint = Color.white;
    643
    644            this.foregroundDomainMarkers = new HashMap();
    645            this.backgroundDomainMarkers = new HashMap();
    646            this.foregroundRangeMarkers = new HashMap();
    647            this.backgroundRangeMarkers = new HashMap();
    648
    649            this.anchorValue = 0.0;
    650
    651            this.domainCrosshairVisible = false;
    652            this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
    653            this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
    654
    655            this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE;
    656            this.rangeCrosshairValue = 0.0;
    657            this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
    658            this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
    659
    660            this.annotations = new java.util.ArrayList();
    661
    662            this.rangePannable = false;
    663            this.shadowGenerator = null;
    664        }
    665
    666        /**
    667         * Returns a string describing the type of plot.
    668         *
    669         * @return The type.
    670         */
    671        public String getPlotType() {
    672            return localizationResources.getString("Category_Plot");
    673        }
    674
    675        /**
    676         * Returns the orientation of the plot.
    677         *
    678         * @return The orientation of the plot (never <code>null</code>).
    679         *
    680         * @see #setOrientation(PlotOrientation)
    681         */
    682        public PlotOrientation getOrientation() {
    683            return this.orientation;
    684        }
    685
    686        /**
    687         * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
    688         * all registered listeners.
    689         *
    690         * @param orientation  the orientation (<code>null</code> not permitted).
    691         *
    692         * @see #getOrientation()
    693         */
    694        public void setOrientation(PlotOrientation orientation) {
    695            if (orientation == null) {
    696                throw new IllegalArgumentException("Null 'orientation' argument.");
    697            }
    698            this.orientation = orientation;
    699            fireChangeEvent();
    700        }
    701
    702        /**
    703         * Returns the axis offset.
    704         *
    705         * @return The axis offset (never <code>null</code>).
    706         *
    707         * @see #setAxisOffset(RectangleInsets)
    708         */
    709        public RectangleInsets getAxisOffset() {
    710            return this.axisOffset;
    711        }
    712
    713        /**
    714         * Sets the axis offsets (gap between the data area and the axes) and
    715         * sends a {@link PlotChangeEvent} to all registered listeners.
    716         *
    717         * @param offset  the offset (<code>null</code> not permitted).
    718         *
    719         * @see #getAxisOffset()
    720         */
    721        public void setAxisOffset(RectangleInsets offset) {
    722            if (offset == null) {
    723                throw new IllegalArgumentException("Null 'offset' argument.");
    724            }
    725            this.axisOffset = offset;
    726            fireChangeEvent();
    727        }
    728
    729        /**
    730         * Returns the domain axis for the plot.  If the domain axis for this plot
    731         * is <code>null</code>, then the method will return the parent plot's
    732         * domain axis (if there is a parent plot).
    733         *
    734         * @return The domain axis (<code>null</code> permitted).
    735         *
    736         * @see #setDomainAxis(CategoryAxis)
    737         */
    738        public CategoryAxis getDomainAxis() {
    739            return getDomainAxis(0);
    740        }
    741
    742        /**
    743         * Returns a domain axis.
    744         *
    745         * @param index  the axis index.
    746         *
    747         * @return The axis (<code>null</code> possible).
    748         *
    749         * @see #setDomainAxis(int, CategoryAxis)
    750         */
    751        public CategoryAxis getDomainAxis(int index) {
    752            CategoryAxis result = null;
    753            if (index < this.domainAxes.size()) {
    754                result = (CategoryAxis) this.domainAxes.get(index);
    755            }
    756            if (result == null) {
    757                Plot parent = getParent();
    758                if (parent instanceof CategoryPlot) {
    759                    CategoryPlot cp = (CategoryPlot) parent;
    760                    result = cp.getDomainAxis(index);
    761                }
    762            }
    763            return result;
    764        }
    765
    766        /**
    767         * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to
    768         * all registered listeners.
    769         *
    770         * @param axis  the axis (<code>null</code> permitted).
    771         *
    772         * @see #getDomainAxis()
    773         */
    774        public void setDomainAxis(CategoryAxis axis) {
    775            setDomainAxis(0, axis);
    776        }
    777
    778        /**
    779         * Sets a domain axis and sends a {@link PlotChangeEvent} to all
    780         * registered listeners.
    781         *
    782         * @param index  the axis index.
    783         * @param axis  the axis (<code>null</code> permitted).
    784         *
    785         * @see #getDomainAxis(int)
    786         */
    787        public void setDomainAxis(int index, CategoryAxis axis) {
    788            setDomainAxis(index, axis, true);
    789        }
    790
    791        /**
    792         * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
    793         * all registered listeners.
    794         *
    795         * @param index  the axis index.
    796         * @param axis  the axis (<code>null</code> permitted).
    797         * @param notify  notify listeners?
    798         */
    799        public void setDomainAxis(int index, CategoryAxis axis, boolean notify) {
    800            CategoryAxis existing = (CategoryAxis) this.domainAxes.get(index);
    801            if (existing != null) {
    802                existing.removeChangeListener(this);
    803            }
    804            if (axis != null) {
    805                axis.setPlot(this);
    806            }
    807            this.domainAxes.set(index, axis);
    808            if (axis != null) {
    809                axis.configure();
    810                axis.addChangeListener(this);
    811            }
    812            if (notify) {
    813                fireChangeEvent();
    814            }
    815        }
    816
    817        /**
    818         * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
    819         * to all registered listeners.
    820         *
    821         * @param axes  the axes (<code>null</code> not permitted).
    822         *
    823         * @see #setRangeAxes(ValueAxis[])
    824         */
    825        public void setDomainAxes(CategoryAxis[] axes) {
    826            for (int i = 0; i < axes.length; i++) {
    827                setDomainAxis(i, axes[i], false);
    828            }
    829            fireChangeEvent();
    830        }
    831
    832        /**
    833         * Returns the index of the specified axis, or <code>-1</code> if the axis
    834         * is not assigned to the plot.
    835         *
    836         * @param axis  the axis (<code>null</code> not permitted).
    837         *
    838         * @return The axis index.
    839         *
    840         * @see #getDomainAxis(int)
    841         * @see #getRangeAxisIndex(ValueAxis)
    842         *
    843         * @since 1.0.3
    844         */
    845        public int getDomainAxisIndex(CategoryAxis axis) {
    846            if (axis == null) {
    847                throw new IllegalArgumentException("Null 'axis' argument.");
    848            }
    849            return this.domainAxes.indexOf(axis);
    850        }
    851
    852        /**
    853         * Returns the domain axis location for the primary domain axis.
    854         *
    855         * @return The location (never <code>null</code>).
    856         *
    857         * @see #getRangeAxisLocation()
    858         */
    859        public AxisLocation getDomainAxisLocation() {
    860            return getDomainAxisLocation(0);
    861        }
    862
    863        /**
    864         * Returns the location for a domain axis.
    865         *
    866         * @param index  the axis index.
    867         *
    868         * @return The location.
    869         *
    870         * @see #setDomainAxisLocation(int, AxisLocation)
    871         */
    872        public AxisLocation getDomainAxisLocation(int index) {
    873            AxisLocation result = null;
    874            if (index < this.domainAxisLocations.size()) {
    875                result = (AxisLocation) this.domainAxisLocations.get(index);
    876            }
    877            if (result == null) {
    878                result = AxisLocation.getOpposite(getDomainAxisLocation(0));
    879            }
    880            return result;
    881        }
    882
    883        /**
    884         * Sets the location of the domain axis and sends a {@link PlotChangeEvent}
    885         * to all registered listeners.
    886         *
    887         * @param location  the axis location (<code>null</code> not permitted).
    888         *
    889         * @see #getDomainAxisLocation()
    890         * @see #setDomainAxisLocation(int, AxisLocation)
    891         */
    892        public void setDomainAxisLocation(AxisLocation location) {
    893            // delegate...
    894            setDomainAxisLocation(0, location, true);
    895        }
    896
    897        /**
    898         * Sets the location of the domain axis and, if requested, sends a
    899         * {@link PlotChangeEvent} to all registered listeners.
    900         *
    901         * @param location  the axis location (<code>null</code> not permitted).
    902         * @param notify  a flag that controls whether listeners are notified.
    903         */
    904        public void setDomainAxisLocation(AxisLocation location, boolean notify) {
    905            // delegate...
    906            setDomainAxisLocation(0, location, notify);
    907        }
    908
    909        /**
    910         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
    911         * to all registered listeners.
    912         *
    913         * @param index  the axis index.
    914         * @param location  the location.
    915         *
    916         * @see #getDomainAxisLocation(int)
    917         * @see #setRangeAxisLocation(int, AxisLocation)
    918         */
    919        public void setDomainAxisLocation(int index, AxisLocation location) {
    920            // delegate...
    921            setDomainAxisLocation(index, location, true);
    922        }
    923
    924        /**
    925         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
    926         * to all registered listeners.
    927         *
    928         * @param index  the axis index.
    929         * @param location  the location.
    930         * @param notify  notify listeners?
    931         *
    932         * @since 1.0.5
    933         *
    934         * @see #getDomainAxisLocation(int)
    935         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
    936         */
    937        public void setDomainAxisLocation(int index, AxisLocation location,
    938                boolean notify) {
    939            if (index == 0 && location == null) {
    940                throw new IllegalArgumentException(
    941                        "Null 'location' for index 0 not permitted.");
    942            }
    943            this.domainAxisLocations.set(index, location);
    944            if (notify) {
    945                fireChangeEvent();
    946            }
    947        }
    948
    949        /**
    950         * Returns the domain axis edge.  This is derived from the axis location
    951         * and the plot orientation.
    952         *
    953         * @return The edge (never <code>null</code>).
    954         */
    955        public RectangleEdge getDomainAxisEdge() {
    956            return getDomainAxisEdge(0);
    957        }
    958
    959        /**
    960         * Returns the edge for a domain axis.
    961         *
    962         * @param index  the axis index.
    963         *
    964         * @return The edge (never <code>null</code>).
    965         */
    966        public RectangleEdge getDomainAxisEdge(int index) {
    967            RectangleEdge result = null;
    968            AxisLocation location = getDomainAxisLocation(index);
    969            if (location != null) {
    970                result = Plot.resolveDomainAxisLocation(location, this.orientation);
    971            }
    972            else {
    973                result = RectangleEdge.opposite(getDomainAxisEdge(0));
    974            }
    975            return result;
    976        }
    977
    978        /**
    979         * Returns the number of domain axes.
    980         *
    981         * @return The axis count.
    982         */
    983        public int getDomainAxisCount() {
    984            return this.domainAxes.size();
    985        }
    986
    987        /**
    988         * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
    989         * to all registered listeners.
    990         */
    991        public void clearDomainAxes() {
    992            for (int i = 0; i < this.domainAxes.size(); i++) {
    993                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
    994                if (axis != null) {
    995                    axis.removeChangeListener(this);
    996                }
    997            }
    998            this.domainAxes.clear();
    999            fireChangeEvent();
    1000        }
    1001
    1002        /**
    1003         * Configures the domain axes.
    1004         */
    1005        public void configureDomainAxes() {
    1006            for (int i = 0; i < this.domainAxes.size(); i++) {
    1007                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
    1008                if (axis != null) {
    1009                    axis.configure();
    1010                }
    1011            }
    1012        }
    1013
    1014        /**
    1015         * Returns the range axis for the plot.  If the range axis for this plot is
    1016         * null, then the method will return the parent plot's range axis (if there
    1017         * is a parent plot).
    1018         *
    1019         * @return The range axis (possibly <code>null</code>).
    1020         */
    1021        public ValueAxis getRangeAxis() {
    1022            return getRangeAxis(0);
    1023        }
    1024
    1025        /**
    1026         * Returns a range axis.
    1027         *
    1028         * @param index  the axis index.
    1029         *
    1030         * @return The axis (<code>null</code> possible).
    1031         */
    1032        public ValueAxis getRangeAxis(int index) {
    1033            ValueAxis result = null;
    1034            if (index < this.rangeAxes.size()) {
    1035                result = (ValueAxis) this.rangeAxes.get(index);
    1036            }
    1037            if (result == null) {
    1038                Plot parent = getParent();
    1039                if (parent instanceof CategoryPlot) {
    1040                    CategoryPlot cp = (CategoryPlot) parent;
    1041                    result = cp.getRangeAxis(index);
    1042                }
    1043            }
    1044            return result;
    1045        }
    1046
    1047        /**
    1048         * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
    1049         * all registered listeners.
    1050         *
    1051         * @param axis  the axis (<code>null</code> permitted).
    1052         */
    1053        public void setRangeAxis(ValueAxis axis) {
    1054            setRangeAxis(0, axis);
    1055        }
    1056
    1057        /**
    1058         * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
    1059         * listeners.
    1060         *
    1061         * @param index  the axis index.
    1062         * @param axis  the axis.
    1063         */
    1064        public void setRangeAxis(int index, ValueAxis axis) {
    1065            setRangeAxis(index, axis, true);
    1066        }
    1067
    1068        /**
    1069         * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
    1070         * all registered listeners.
    1071         *
    1072         * @param index  the axis index.
    1073         * @param axis  the axis.
    1074         * @param notify  notify listeners?
    1075         */
    1076        public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
    1077            ValueAxis existing = (ValueAxis) this.rangeAxes.get(index);
    1078            if (existing != null) {
    1079                existing.removeChangeListener(this);
    1080            }
    1081            if (axis != null) {
    1082                axis.setPlot(this);
    1083            }
    1084            this.rangeAxes.set(index, axis);
    1085            if (axis != null) {
    1086                axis.configure();
    1087                axis.addChangeListener(this);
    1088            }
    1089            if (notify) {
    1090                fireChangeEvent();
    1091            }
    1092        }
    1093
    1094        /**
    1095         * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
    1096         * to all registered listeners.
    1097         *
    1098         * @param axes  the axes (<code>null</code> not permitted).
    1099         *
    1100         * @see #setDomainAxes(CategoryAxis[])
    1101         */
    1102        public void setRangeAxes(ValueAxis[] axes) {
    1103            for (int i = 0; i < axes.length; i++) {
    1104                setRangeAxis(i, axes[i], false);
    1105            }
    1106            fireChangeEvent();
    1107        }
    1108
    1109        /**
    1110         * Returns the index of the specified axis, or <code>-1</code> if the axis
    1111         * is not assigned to the plot.
    1112         *
    1113         * @param axis  the axis (<code>null</code> not permitted).
    1114         *
    1115         * @return The axis index.
    1116         *
    1117         * @see #getRangeAxis(int)
    1118         * @see #getDomainAxisIndex(CategoryAxis)
    1119         *
    1120         * @since 1.0.7
    1121         */
    1122        public int getRangeAxisIndex(ValueAxis axis) {
    1123            if (axis == null) {
    1124                throw new IllegalArgumentException("Null 'axis' argument.");
    1125            }
    1126            int result = this.rangeAxes.indexOf(axis);
    1127            if (result < 0) { // try the parent plot
    1128                Plot parent = getParent();
    1129                if (parent instanceof CategoryPlot) {
    1130                    CategoryPlot p = (CategoryPlot) parent;
    1131                    result = p.getRangeAxisIndex(axis);
    1132                }
    1133            }
    1134            return result;
    1135        }
    1136
    1137        /**
    1138         * Returns the range axis location.
    1139         *
    1140         * @return The location (never <code>null</code>).
    1141         */
    1142        public AxisLocation getRangeAxisLocation() {
    1143            return getRangeAxisLocation(0);
    1144        }
    1145
    1146        /**
    1147         * Returns the location for a range axis.
    1148         *
    1149         * @param index  the axis index.
    1150         *
    1151         * @return The location.
    1152         *
    1153         * @see #setRangeAxisLocation(int, AxisLocation)
    1154         */
    1155        public AxisLocation getRangeAxisLocation(int index) {
    1156            AxisLocation result = null;
    1157            if (index < this.rangeAxisLocations.size()) {
    1158                result = (AxisLocation) this.rangeAxisLocations.get(index);
    1159            }
    1160            if (result == null) {
    1161                result = AxisLocation.getOpposite(getRangeAxisLocation(0));
    1162            }
    1163            return result;
    1164        }
    1165
    1166        /**
    1167         * Sets the location of the range axis and sends a {@link PlotChangeEvent}
    1168         * to all registered listeners.
    1169         *
    1170         * @param location  the location (<code>null</code> not permitted).
    1171         *
    1172         * @see #setRangeAxisLocation(AxisLocation, boolean)
    1173         * @see #setDomainAxisLocation(AxisLocation)
    1174         */
    1175        public void setRangeAxisLocation(AxisLocation location) {
    1176            // defer argument checking...
    1177            setRangeAxisLocation(location, true);
    1178        }
    1179
    1180        /**
    1181         * Sets the location of the range axis and, if requested, sends a
    1182         * {@link PlotChangeEvent} to all registered listeners.
    1183         *
    1184         * @param location  the location (<code>null</code> not permitted).
    1185         * @param notify  notify listeners?
    1186         *
    1187         * @see #setDomainAxisLocation(AxisLocation, boolean)
    1188         */
    1189        public void setRangeAxisLocation(AxisLocation location, boolean notify) {
    1190            setRangeAxisLocation(0, location, notify);
    1191        }
    1192
    1193        /**
    1194         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
    1195         * to all registered listeners.
    1196         *
    1197         * @param index  the axis index.
    1198         * @param location  the location.
    1199         *
    1200         * @see #getRangeAxisLocation(int)
    1201         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
    1202         */
    1203        public void setRangeAxisLocation(int index, AxisLocation location) {
    1204            setRangeAxisLocation(index, location, true);
    1205        }
    1206
    1207        /**
    1208         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
    1209         * to all registered listeners.
    1210         *
    1211         * @param index  the axis index.
    1212         * @param location  the location.
    1213         * @param notify  notify listeners?
    1214         *
    1215         * @see #getRangeAxisLocation(int)
    1216         * @see #setDomainAxisLocation(int, AxisLocation, boolean)
    1217         */
    1218        public void setRangeAxisLocation(int index, AxisLocation location,
    1219                                         boolean notify) {
    1220            if (index == 0 && location == null) {
    1221                throw new IllegalArgumentException(
    1222                        "Null 'location' for index 0 not permitted.");
    1223            }
    1224            this.rangeAxisLocations.set(index, location);
    1225            if (notify) {
    1226                fireChangeEvent();
    1227            }
    1228        }
    1229
    1230        /**
    1231         * Returns the edge where the primary range axis is located.
    1232         *
    1233         * @return The edge (never <code>null</code>).
    1234         */
    1235        public RectangleEdge getRangeAxisEdge() {
    1236            return getRangeAxisEdge(0);
    1237        }
    1238
    1239        /**
    1240         * Returns the edge for a range axis.
    1241         *
    1242         * @param index  the axis index.
    1243         *
    1244         * @return The edge.
    1245         */
    1246        public RectangleEdge getRangeAxisEdge(int index) {
    1247            AxisLocation location = getRangeAxisLocation(index);
    1248            RectangleEdge result = Plot.resolveRangeAxisLocation(location,
    1249                    this.orientation);
    1250            if (result == null) {
    1251                result = RectangleEdge.opposite(getRangeAxisEdge(0));
    1252            }
    1253            return result;
    1254        }
    1255
    1256        /**
    1257         * Returns the number of range axes.
    1258         *
    1259         * @return The axis count.
    1260         */
    1261        public int getRangeAxisCount() {
    1262            return this.rangeAxes.size();
    1263        }
    1264
    1265        /**
    1266         * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
    1267         * to all registered listeners.
    1268         */
    1269        public void clearRangeAxes() {
    1270            for (int i = 0; i < this.rangeAxes.size(); i++) {
    1271                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
    1272                if (axis != null) {
    1273                    axis.removeChangeListener(this);
    1274                }
    1275            }
    1276            this.rangeAxes.clear();
    1277            fireChangeEvent();
    1278        }
    1279
    1280        /**
    1281         * Configures the range axes.
    1282         */
    1283        public void configureRangeAxes() {
    1284            for (int i = 0; i < this.rangeAxes.size(); i++) {
    1285                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
    1286                if (axis != null) {
    1287                    axis.configure();
    1288                }
    1289            }
    1290        }
    1291
    1292        /**
    1293         * Returns the primary dataset for the plot.
    1294         *
    1295         * @return The primary dataset (possibly <code>null</code>).
    1296         *
    1297         * @see #setDataset(CategoryDataset)
    1298         */
    1299        public CategoryDataset getDataset() {
    1300            return getDataset(0);
    1301        }
    1302
    1303        /**
    1304         * Returns the dataset at the given index.
    1305         *
    1306         * @param index  the dataset index.
    1307         *
    1308         * @return The dataset (possibly <code>null</code>).
    1309         *
    1310         * @see #setDataset(int, CategoryDataset)
    1311         */
    1312        public CategoryDataset getDataset(int index) {
    1313            CategoryDataset result = null;
    1314            if (this.datasets.size() > index) {
    1315                result = (CategoryDataset) this.datasets.get(index);
    1316            }
    1317            return result;
    1318        }
    1319
    1320        /**
    1321         * Sets the dataset for the plot, replacing the existing dataset, if there
    1322         * is one.  This method also calls the
    1323         * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the
    1324         * axis ranges if necessary and sends a {@link PlotChangeEvent} to all
    1325         * registered listeners.
    1326         *
    1327         * @param dataset  the dataset (<code>null</code> permitted).
    1328         *
    1329         * @see #getDataset()
    1330         */
    1331        public void setDataset(CategoryDataset dataset) {
    1332            setDataset(0, dataset);
    1333        }
    1334
    1335        /**
    1336         * Sets a dataset for the plot.
    1337         *
    1338         * @param index  the dataset index.
    1339         * @param dataset  the dataset (<code>null</code> permitted).
    1340         *
    1341         * @see #getDataset(int)
    1342         */
    1343        public void setDataset(int index, CategoryDataset dataset) {
    1344
    1345            CategoryDataset existing = (CategoryDataset) this.datasets.get(index);
    1346            if (existing != null) {
    1347                existing.removeChangeListener(this);
    1348            }
    1349            this.datasets.set(index, dataset);
    1350            if (dataset != null) {
    1351                dataset.addChangeListener(this);
    1352            }
    1353
    1354            // send a dataset change event to self...
    1355            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
    1356            datasetChanged(event);
    1357
    1358        }
    1359
    1360        /**
    1361         * Returns the number of datasets.
    1362         *
    1363         * @return The number of datasets.
    1364         *
    1365         * @since 1.0.2
    1366         */
    1367        public int getDatasetCount() {
    1368            return this.datasets.size();
    1369        }
    1370
    1371        /**
    1372         * Returns the index of the specified dataset, or <code>-1</code> if the
    1373         * dataset does not belong to the plot.
    1374         *
    1375         * @param dataset  the dataset (<code>null</code> not permitted).
    1376         *
    1377         * @return The index.
    1378         *
    1379         * @since 1.0.11
    1380         */
    1381        public int indexOf(CategoryDataset dataset) {
    1382            int result = -1;
    1383            for (int i = 0; i < this.datasets.size(); i++) {
    1384                if (dataset == this.datasets.get(i)) {
    1385                    result = i;
    1386                    break;
    1387                }
    1388            }
    1389            return result;
    1390        }
    1391
    1392        /**
    1393         * Maps a dataset to a particular domain axis.
    1394         *
    1395         * @param index  the dataset index (zero-based).
    1396         * @param axisIndex  the axis index (zero-based).
    1397         *
    1398         * @see #getDomainAxisForDataset(int)
    1399         */
    1400        public void mapDatasetToDomainAxis(int index, int axisIndex) {
    1401            List axisIndices = new java.util.ArrayList(1);
    1402            axisIndices.add(new Integer(axisIndex));
    1403            mapDatasetToDomainAxes(index, axisIndices);
    1404        }
    1405
    1406        /**
    1407         * Maps the specified dataset to the axes in the list.  Note that the
    1408         * conversion of data values into Java2D space is always performed using
    1409         * the first axis in the list.
    1410         *
    1411         * @param index  the dataset index (zero-based).
    1412         * @param axisIndices  the axis indices (<code>null</code> permitted).
    1413         *
    1414         * @since 1.0.12
    1415         */
    1416        public void mapDatasetToDomainAxes(int index, List axisIndices) {
    1417            if (index < 0) {
    1418                throw new IllegalArgumentException("Requires 'index' >= 0.");
    1419            }
    1420            checkAxisIndices(axisIndices);
    1421            Integer key = new Integer(index);
    1422            this.datasetToDomainAxesMap.put(key, new ArrayList(axisIndices));
    1423            // fake a dataset change event to update axes...
    1424            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
    1425        }
    1426
    1427        /**
    1428         * This method is used to perform argument checking on the list of
    1429         * axis indices passed to mapDatasetToDomainAxes() and
    1430         * mapDatasetToRangeAxes().
    1431         *
    1432         * @param indices  the list of indices (<code>null</code> permitted).
    1433         */
    1434        private void checkAxisIndices(List indices) {
    1435            // axisIndices can be:
    1436            // 1.  null;
    1437            // 2.  non-empty, containing only Integer objects that are unique.
    1438            if (indices == null) {
    1439                return;  // OK
    1440            }
    1441            int count = indices.size();
    1442            if (count == 0) {
    1443                throw new IllegalArgumentException("Empty list not permitted.");
    1444            }
    1445            HashSet set = new HashSet();
    1446            for (int i = 0; i < count; i++) {
    1447                Object item = indices.get(i);
    1448                if (!(item instanceof Integer)) {
    1449                    throw new IllegalArgumentException(
    1450                            "Indices must be Integer instances.");
    1451                }
    1452                if (set.contains(item)) {
    1453                    throw new IllegalArgumentException("Indices must be unique.");
    1454                }
    1455                set.add(item);
    1456            }
    1457        }
    1458
    1459        /**
    1460         * Returns the domain axis for a dataset.  You can change the axis for a
    1461         * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method.
    1462         *
    1463         * @param index  the dataset index.
    1464         *
    1465         * @return The domain axis.
    1466         *
    1467         * @see #mapDatasetToDomainAxis(int, int)
    1468         */
    1469        public CategoryAxis getDomainAxisForDataset(int index) {
    1470            if (index < 0) {
    1471                throw new IllegalArgumentException("Negative 'index'.");
    1472            }
    1473            CategoryAxis axis = null;
    1474            List axisIndices = (List) this.datasetToDomainAxesMap.get(
    1475                    new Integer(index));
    1476            if (axisIndices != null) {
    1477                // the first axis in the list is used for data <--> Java2D
    1478                Integer axisIndex = (Integer) axisIndices.get(0);
    1479                axis = getDomainAxis(axisIndex.intValue());
    1480            }
    1481            else {
    1482                axis = getDomainAxis(0);
    1483            }
    1484            return axis;
    1485        }
    1486
    1487        /**
    1488         * Maps a dataset to a particular range axis.
    1489         *
    1490         * @param index  the dataset index (zero-based).
    1491         * @param axisIndex  the axis index (zero-based).
    1492         *
    1493         * @see #getRangeAxisForDataset(int)
    1494         */
    1495        public void mapDatasetToRangeAxis(int index, int axisIndex) {
    1496            List axisIndices = new java.util.ArrayList(1);
    1497            axisIndices.add(new Integer(axisIndex));
    1498            mapDatasetToRangeAxes(index, axisIndices);
    1499        }
    1500
    1501        /**
    1502         * Maps the specified dataset to the axes in the list.  Note that the
    1503         * conversion of data values into Java2D space is always performed using
    1504         * the first axis in the list.
    1505         *
    1506         * @param index  the dataset index (zero-based).
    1507         * @param axisIndices  the axis indices (<code>null</code> permitted).
    1508         *
    1509         * @since 1.0.12
    1510         */
    1511        public void mapDatasetToRangeAxes(int index, List axisIndices) {
    1512            if (index < 0) {
    1513                throw new IllegalArgumentException("Requires 'index' >= 0.");
    1514            }
    1515            checkAxisIndices(axisIndices);
    1516            Integer key = new Integer(index);
    1517            this.datasetToRangeAxesMap.put(key, new ArrayList(axisIndices));
    1518            // fake a dataset change event to update axes...
    1519            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
    1520        }
    1521
    1522        /**
    1523         * Returns the range axis for a dataset.  You can change the axis for a
    1524         * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method.
    1525         *
    1526         * @param index  the dataset index.
    1527         *
    1528         * @return The range axis.
    1529         *
    1530         * @see #mapDatasetToRangeAxis(int, int)
    1531         */
    1532        public ValueAxis getRangeAxisForDataset(int index) {
    1533            if (index < 0) {
    1534                throw new IllegalArgumentException("Negative 'index'.");
    1535            }
    1536            ValueAxis axis = null;
    1537            List axisIndices = (List) this.datasetToRangeAxesMap.get(
    1538                    new Integer(index));
    1539            if (axisIndices != null) {
    1540                // the first axis in the list is used for data <--> Java2D
    1541                Integer axisIndex = (Integer) axisIndices.get(0);
    1542                axis = getRangeAxis(axisIndex.intValue());
    1543            }
    1544            else {
    1545                axis = getRangeAxis(0);
    1546            }
    1547            return axis;
    1548        }
    1549
    1550        /**
    1551         * Returns the number of renderer slots for this plot.
    1552         *
    1553         * @return The number of renderer slots.
    1554         *
    1555         * @since 1.0.11
    1556         */
    1557        public int getRendererCount() {
    1558            return this.renderers.size();
    1559        }
    1560
    1561        /**
    1562         * Returns a reference to the renderer for the plot.
    1563         *
    1564         * @return The renderer.
    1565         *
    1566         * @see #setRenderer(CategoryItemRenderer)
    1567         */
    1568        public CategoryItemRenderer getRenderer() {
    1569            return getRenderer(0);
    1570        }
    1571
    1572        /**
    1573         * Returns the renderer at the given index.
    1574         *
    1575         * @param index  the renderer index.
    1576         *
    1577         * @return The renderer (possibly <code>null</code>).
    1578         *
    1579         * @see #setRenderer(int, CategoryItemRenderer)
    1580         */
    1581        public CategoryItemRenderer getRenderer(int index) {
    1582            CategoryItemRenderer result = null;
    1583            if (this.renderers.size() > index) {
    1584                result = (CategoryItemRenderer) this.renderers.get(index);
    1585            }
    1586            return result;
    1587        }
    1588
    1589        /**
    1590         * Sets the renderer at index 0 (sometimes referred to as the "primary"
    1591         * renderer) and sends a {@link PlotChangeEvent} to all registered
    1592         * listeners.
    1593         *
    1594         * @param renderer  the renderer (<code>null</code> permitted.
    1595         *
    1596         * @see #getRenderer()
    1597         */
    1598        public void setRenderer(CategoryItemRenderer renderer) {
    1599            setRenderer(0, renderer, true);
    1600        }
    1601
    1602        /**
    1603         * Sets the renderer at index 0 (sometimes referred to as the "primary"
    1604         * renderer) and, if requested, sends a {@link PlotChangeEvent} to all
    1605         * registered listeners.
    1606         * <p>
    1607         * You can set the renderer to <code>null</code>, but this is not
    1608         * recommended because:
    1609         * <ul>
    1610         *   <li>no data will be displayed;</li>
    1611         *   <li>the plot background will not be painted;</li>
    1612         * </ul>
    1613         *
    1614         * @param renderer  the renderer (<code>null</code> permitted).
    1615         * @param notify  notify listeners?
    1616         *
    1617         * @see #getRenderer()
    1618         */
    1619        public void setRenderer(CategoryItemRenderer renderer, boolean notify) {
    1620            setRenderer(0, renderer, notify);
    1621        }
    1622
    1623        /**
    1624         * Sets the renderer at the specified index and sends a
    1625         * {@link PlotChangeEvent} to all registered listeners.
    1626         *
    1627         * @param index  the index.
    1628         * @param renderer  the renderer (<code>null</code> permitted).
    1629         *
    1630         * @see #getRenderer(int)
    1631         * @see #setRenderer(int, CategoryItemRenderer, boolean)
    1632         */
    1633        public void setRenderer(int index, CategoryItemRenderer renderer) {
    1634            setRenderer(index, renderer, true);
    1635        }
    1636
    1637        /**
    1638         * Sets a renderer.  A {@link PlotChangeEvent} is sent to all registered
    1639         * listeners.
    1640         *
    1641         * @param index  the index.
    1642         * @param renderer  the renderer (<code>null</code> permitted).
    1643         * @param notify  notify listeners?
    1644         *
    1645         * @see #getRenderer(int)
    1646         */
    1647        public void setRenderer(int index, CategoryItemRenderer renderer,
    1648                                boolean notify) {
    1649
    1650            // stop listening to the existing renderer...
    1651            CategoryItemRenderer existing
    1652                = (CategoryItemRenderer) this.renderers.get(index);
    1653            if (existing != null) {
    1654                existing.removeChangeListener(this);
    1655            }
    1656
    1657            // register the new renderer...
    1658            this.renderers.set(index, renderer);
    1659            if (renderer != null) {
    1660                renderer.setPlot(this);
    1661                renderer.addChangeListener(this);
    1662            }
    1663
    1664            configureDomainAxes();
    1665            configureRangeAxes();
    1666
    1667            if (notify) {
    1668                fireChangeEvent();
    1669            }
    1670        }
    1671
    1672        /**
    1673         * Sets the renderers for this plot and sends a {@link PlotChangeEvent}
    1674         * to all registered listeners.
    1675         *
    1676         * @param renderers  the renderers.
    1677         */
    1678        public void setRenderers(CategoryItemRenderer[] renderers) {
    1679            for (int i = 0; i < renderers.length; i++) {
    1680                setRenderer(i, renderers[i], false);
    1681            }
    1682            fireChangeEvent();
    1683        }
    1684
    1685        /**
    1686         * Returns the renderer for the specified dataset.  If the dataset doesn't
    1687         * belong to the plot, this method will return <code>null</code>.
    1688         *
    1689         * @param dataset  the dataset (<code>null</code> permitted).
    1690         *
    1691         * @return The renderer (possibly <code>null</code>).
    1692         */
    1693        public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) {
    1694            CategoryItemRenderer result = null;
    1695            for (int i = 0; i < this.datasets.size(); i++) {
    1696                if (this.datasets.get(i) == dataset) {
    1697                    result = (CategoryItemRenderer) this.renderers.get(i);
    1698                    break;
    1699                }
    1700            }
    1701            return result;
    1702        }
    1703
    1704        /**
    1705         * Returns the index of the specified renderer, or <code>-1</code> if the
    1706         * renderer is not assigned to this plot.
    1707         *
    1708         * @param renderer  the renderer (<code>null</code> permitted).
    1709         *
    1710         * @return The renderer index.
    1711         */
    1712        public int getIndexOf(CategoryItemRenderer renderer) {
    1713            return this.renderers.indexOf(renderer);
    1714        }
    1715
    1716        /**
    1717         * Returns the dataset rendering order.
    1718         *
    1719         * @return The order (never <code>null</code>).
    1720         *
    1721         * @see #setDatasetRenderingOrder(DatasetRenderingOrder)
    1722         */
    1723        public DatasetRenderingOrder getDatasetRenderingOrder() {
    1724            return this.renderingOrder;
    1725        }
    1726
    1727        /**
    1728         * Sets the rendering order and sends a {@link PlotChangeEvent} to all
    1729         * registered listeners.  By default, the plot renders the primary dataset
    1730         * last (so that the primary dataset overlays the secondary datasets).  You
    1731         * can reverse this if you want to.
    1732         *
    1733         * @param order  the rendering order (<code>null</code> not permitted).
    1734         *
    1735         * @see #getDatasetRenderingOrder()
    1736         */
    1737        public void setDatasetRenderingOrder(DatasetRenderingOrder order) {
    1738            if (order == null) {
    1739                throw new IllegalArgumentException("Null 'order' argument.");
    1740            }
    1741            this.renderingOrder = order;
    1742            fireChangeEvent();
    1743        }
    1744
    1745        /**
    1746         * Returns the order in which the columns are rendered.  The default value
    1747         * is <code>SortOrder.ASCENDING</code>.
    1748         *
    1749         * @return The column rendering order (never <code>null</code).
    1750         *
    1751         * @see #setColumnRenderingOrder(SortOrder)
    1752         */
    1753        public SortOrder getColumnRenderingOrder() {
    1754            return this.columnRenderingOrder;
    1755        }
    1756
    1757        /**
    1758         * Sets the column order in which the items in each dataset should be
    1759         * rendered and sends a {@link PlotChangeEvent} to all registered
    1760         * listeners.  Note that this affects the order in which items are drawn,
    1761         * NOT their position in the chart.
    1762         *
    1763         * @param order  the order (<code>null</code> not permitted).
    1764         *
    1765         * @see #getColumnRenderingOrder()
    1766         * @see #setRowRenderingOrder(SortOrder)
    1767         */
    1768        public void setColumnRenderingOrder(SortOrder order) {
    1769            if (order == null) {
    1770                throw new IllegalArgumentException("Null 'order' argument.");
    1771            }
    1772            this.columnRenderingOrder = order;
    1773            fireChangeEvent();
    1774        }
    1775
    1776        /**
    1777         * Returns the order in which the rows should be rendered.  The default
    1778         * value is <code>SortOrder.ASCENDING</code>.
    1779         *
    1780         * @return The order (never <code>null</code>).
    1781         *
    1782         * @see #setRowRenderingOrder(SortOrder)
    1783         */
    1784        public SortOrder getRowRenderingOrder() {
    1785            return this.rowRenderingOrder;
    1786        }
    1787
    1788        /**
    1789         * Sets the row order in which the items in each dataset should be
    1790         * rendered and sends a {@link PlotChangeEvent} to all registered
    1791         * listeners.  Note that this affects the order in which items are drawn,
    1792         * NOT their position in the chart.
    1793         *
    1794         * @param order  the order (<code>null</code> not permitted).
    1795         *
    1796         * @see #getRowRenderingOrder()
    1797         * @see #setColumnRenderingOrder(SortOrder)
    1798         */
    1799        public void setRowRenderingOrder(SortOrder order) {
    1800            if (order == null) {
    1801                throw new IllegalArgumentException("Null 'order' argument.");
    1802            }
    1803            this.rowRenderingOrder = order;
    1804            fireChangeEvent();
    1805        }
    1806
    1807        /**
    1808         * Returns the flag that controls whether the domain grid-lines are visible.
    1809         *
    1810         * @return The <code>true</code> or <code>false</code>.
    1811         *
    1812         * @see #setDomainGridlinesVisible(boolean)
    1813         */
    1814        public boolean isDomainGridlinesVisible() {
    1815            return this.domainGridlinesVisible;
    1816        }
    1817
    1818        /**
    1819         * Sets the flag that controls whether or not grid-lines are drawn against
    1820         * the domain axis.
    1821         * <p>
    1822         * If the flag value changes, a {@link PlotChangeEvent} is sent to all
    1823         * registered listeners.
    1824         *
    1825         * @param visible  the new value of the flag.
    1826         *
    1827         * @see #isDomainGridlinesVisible()
    1828         */
    1829        public void setDomainGridlinesVisible(boolean visible) {
    1830            if (this.domainGridlinesVisible != visible) {
    1831                this.domainGridlinesVisible = visible;
    1832                fireChangeEvent();
    1833            }
    1834        }
    1835
    1836        /**
    1837         * Returns the position used for the domain gridlines.
    1838         *
    1839         * @return The gridline position (never <code>null</code>).
    1840         *
    1841         * @see #setDomainGridlinePosition(CategoryAnchor)
    1842         */
    1843        public CategoryAnchor getDomainGridlinePosition() {
    1844            return this.domainGridlinePosition;
    1845        }
    1846
    1847        /**
    1848         * Sets the position used for the domain gridlines and sends a
    1849         * {@link PlotChangeEvent} to all registered listeners.
    1850         *
    1851         * @param position  the position (<code>null</code> not permitted).
    1852         *
    1853         * @see #getDomainGridlinePosition()
    1854         */
    1855        public void setDomainGridlinePosition(CategoryAnchor position) {
    1856            if (position == null) {
    1857                throw new IllegalArgumentException("Null 'position' argument.");
    1858            }
    1859            this.domainGridlinePosition = position;
    1860            fireChangeEvent();
    1861        }
    1862
    1863        /**
    1864         * Returns the stroke used to draw grid-lines against the domain axis.
    1865         *
    1866         * @return The stroke (never <code>null</code>).
    1867         *
    1868         * @see #setDomainGridlineStroke(Stroke)
    1869         */
    1870        public Stroke getDomainGridlineStroke() {
    1871            return this.domainGridlineStroke;
    1872        }
    1873
    1874        /**
    1875         * Sets the stroke used to draw grid-lines against the domain axis and
    1876         * sends a {@link PlotChangeEvent} to all registered listeners.
    1877         *
    1878         * @param stroke  the stroke (<code>null</code> not permitted).
    1879         *
    1880         * @see #getDomainGridlineStroke()
    1881         */
    1882        public void setDomainGridlineStroke(Stroke stroke) {
    1883            if (stroke == null) {
    1884                throw new IllegalArgumentException("Null 'stroke' not permitted.");
    1885            }
    1886            this.domainGridlineStroke = stroke;
    1887            fireChangeEvent();
    1888        }
    1889
    1890        /**
    1891         * Returns the paint used to draw grid-lines against the domain axis.
    1892         *
    1893         * @return The paint (never <code>null</code>).
    1894         *
    1895         * @see #setDomainGridlinePaint(Paint)
    1896         */
    1897        public Paint getDomainGridlinePaint() {
    1898            return this.domainGridlinePaint;
    1899        }
    1900
    1901        /**
    1902         * Sets the paint used to draw the grid-lines (if any) against the domain
    1903         * axis and sends a {@link PlotChangeEvent} to all registered listeners.
    1904         *
    1905         * @param paint  the paint (<code>null</code> not permitted).
    1906         *
    1907         * @see #getDomainGridlinePaint()
    1908         */
    1909        public void setDomainGridlinePaint(Paint paint) {
    1910            if (paint == null) {
    1911                throw new IllegalArgumentException("Null 'paint' argument.");
    1912            }
    1913            this.domainGridlinePaint = paint;
    1914            fireChangeEvent();
    1915        }
    1916
    1917        /**
    1918         * Returns a flag that controls whether or not a zero baseline is
    1919         * displayed for the range axis.
    1920         *
    1921         * @return A boolean.
    1922         *
    1923         * @see #setRangeZeroBaselineVisible(boolean)
    1924         *
    1925         * @since 1.0.13
    1926         */
    1927        public boolean isRangeZeroBaselineVisible() {
    1928            return this.rangeZeroBaselineVisible;
    1929        }
    1930
    1931        /**
    1932         * Sets the flag that controls whether or not the zero baseline is
    1933         * displayed for the range axis, and sends a {@link PlotChangeEvent} to
    1934         * all registered listeners.
    1935         *
    1936         * @param visible  the flag.
    1937         *
    1938         * @see #isRangeZeroBaselineVisible()
    1939         *
    1940         * @since 1.0.13
    1941         */
    1942        public void setRangeZeroBaselineVisible(boolean visible) {
    1943            this.rangeZeroBaselineVisible = visible;
    1944            fireChangeEvent();
    1945        }
    1946
    1947        /**
    1948         * Returns the stroke used for the zero baseline against the range axis.
    1949         *
    1950         * @return The stroke (never <code>null</code>).
    1951         *
    1952         * @see #setRangeZeroBaselineStroke(Stroke)
    1953         *
    1954         * @since 1.0.13
    1955         */
    1956        public Stroke getRangeZeroBaselineStroke() {
    1957            return this.rangeZeroBaselineStroke;
    1958        }
    1959
    1960        /**
    1961         * Sets the stroke for the zero baseline for the range axis,
    1962         * and sends a {@link PlotChangeEvent} to all registered listeners.
    1963         *
    1964         * @param stroke  the stroke (<code>null</code> not permitted).
    1965         *
    1966         * @see #getRangeZeroBaselineStroke()
    1967         *
    1968         * @since 1.0.13
    1969         */
    1970        public void setRangeZeroBaselineStroke(Stroke stroke) {
    1971            if (stroke == null) {
    1972                throw new IllegalArgumentException("Null 'stroke' argument.");
    1973            }
    1974            this.rangeZeroBaselineStroke = stroke;
    1975            fireChangeEvent();
    1976        }
    1977
    1978        /**
    1979         * Returns the paint for the zero baseline (if any) plotted against the
    1980         * range axis.
    1981         *
    1982         * @return The paint (never <code>null</code>).
    1983         *
    1984         * @see #setRangeZeroBaselinePaint(Paint)
    1985         *
    1986         * @since 1.0.13
    1987         */
    1988        public Paint getRangeZeroBaselinePaint() {
    1989            return this.rangeZeroBaselinePaint;
    1990        }
    1991
    1992        /**
    1993         * Sets the paint for the zero baseline plotted against the range axis and
    1994         * sends a {@link PlotChangeEvent} to all registered listeners.
    1995         *
    1996         * @param paint  the paint (<code>null</code> not permitted).
    1997         *
    1998         * @see #getRangeZeroBaselinePaint()
    1999         *
    2000         * @since 1.0.13
    2001         */
    2002        public void setRangeZeroBaselinePaint(Paint paint) {
    2003            if (paint == null) {
    2004                throw new IllegalArgumentException("Null 'paint' argument.");
    2005            }
    2006            this.rangeZeroBaselinePaint = paint;
    2007            fireChangeEvent();
    2008        }
    2009
    2010        /**
    2011         * Returns the flag that controls whether the range grid-lines are visible.
    2012         *
    2013         * @return The flag.
    2014         *
    2015         * @see #setRangeGridlinesVisible(boolean)
    2016         */
    2017        public boolean isRangeGridlinesVisible() {
    2018            return this.rangeGridlinesVisible;
    2019        }
    2020
    2021        /**
    2022         * Sets the flag that controls whether or not grid-lines are drawn against
    2023         * the range axis.  If the flag changes value, a {@link PlotChangeEvent} is
    2024         * sent to all registered listeners.
    2025         *
    2026         * @param visible  the new value of the flag.
    2027         *
    2028         * @see #isRangeGridlinesVisible()
    2029         */
    2030        public void setRangeGridlinesVisible(boolean visible) {
    2031            if (this.rangeGridlinesVisible != visible) {
    2032                this.rangeGridlinesVisible = visible;
    2033                fireChangeEvent();
    2034            }
    2035        }
    2036
    2037        /**
    2038         * Returns the stroke used to draw the grid-lines against the range axis.
    2039         *
    2040         * @return The stroke (never <code>null</code>).
    2041         *
    2042         * @see #setRangeGridlineStroke(Stroke)
    2043         */
    2044        public Stroke getRangeGridlineStroke() {
    2045            return this.rangeGridlineStroke;
    2046        }
    2047
    2048        /**
    2049         * Sets the stroke used to draw the grid-lines against the range axis and
    2050         * sends a {@link PlotChangeEvent} to all registered listeners.
    2051         *
    2052         * @param stroke  the stroke (<code>null</code> not permitted).
    2053         *
    2054         * @see #getRangeGridlineStroke()
    2055         */
    2056        public void setRangeGridlineStroke(Stroke stroke) {
    2057            if (stroke == null) {
    2058                throw new IllegalArgumentException("Null 'stroke' argument.");
    2059            }
    2060            this.rangeGridlineStroke = stroke;
    2061            fireChangeEvent();
    2062        }
    2063
    2064        /**
    2065         * Returns the paint used to draw the grid-lines against the range axis.
    2066         *
    2067         * @return The paint (never <code>null</code>).
    2068         *
    2069         * @see #setRangeGridlinePaint(Paint)
    2070         */
    2071        public Paint getRangeGridlinePaint() {
    2072            return this.rangeGridlinePaint;
    2073        }
    2074
    2075        /**
    2076         * Sets the paint used to draw the grid lines against the range axis and
    2077         * sends a {@link PlotChangeEvent} to all registered listeners.
    2078         *
    2079         * @param paint  the paint (<code>null</code> not permitted).
    2080         *
    2081         * @see #getRangeGridlinePaint()
    2082         */
    2083        public void setRangeGridlinePaint(Paint paint) {
    2084            if (paint == null) {
    2085                throw new IllegalArgumentException("Null 'paint' argument.");
    2086            }
    2087            this.rangeGridlinePaint = paint;
    2088            fireChangeEvent();
    2089        }
    2090
    2091        /**
    2092         * Returns <code>true</code> if the range axis minor grid is visible, and
    2093         * <code>false</code> otherwise.
    2094         *
    2095         * @return A boolean.
    2096         *
    2097         * @see #setRangeMinorGridlinesVisible(boolean)
    2098         *
    2099         * @since 1.0.13
    2100         */
    2101        public boolean isRangeMinorGridlinesVisible() {
    2102            return this.rangeMinorGridlinesVisible;
    2103        }
    2104
    2105        /**
    2106         * Sets the flag that controls whether or not the range axis minor grid
    2107         * lines are visible.
    2108         * <p>
    2109         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
    2110         * registered listeners.
    2111         *
    2112         * @param visible  the new value of the flag.
    2113         *
    2114         * @see #isRangeMinorGridlinesVisible()
    2115         *
    2116         * @since 1.0.13
    2117         */
    2118        public void setRangeMinorGridlinesVisible(boolean visible) {
    2119            if (this.rangeMinorGridlinesVisible != visible) {
    2120                this.rangeMinorGridlinesVisible = visible;
    2121                fireChangeEvent();
    2122            }
    2123        }
    2124
    2125        /**
    2126         * Returns the stroke for the minor grid lines (if any) plotted against the
    2127         * range axis.
    2128         *
    2129         * @return The stroke (never <code>null</code>).
    2130         *
    2131         * @see #setRangeMinorGridlineStroke(Stroke)
    2132         *
    2133         * @since 1.0.13
    2134         */
    2135        public Stroke getRangeMinorGridlineStroke() {
    2136            return this.rangeMinorGridlineStroke;
    2137        }
    2138
    2139        /**
    2140         * Sets the stroke for the minor grid lines plotted against the range axis,
    2141         * and sends a {@link PlotChangeEvent} to all registered listeners.
    2142         *
    2143         * @param stroke  the stroke (<code>null</code> not permitted).
    2144         *
    2145         * @see #getRangeMinorGridlineStroke()
    2146         *
    2147         * @since 1.0.13
    2148         */
    2149        public void setRangeMinorGridlineStroke(Stroke stroke) {
    2150            if (stroke == null) {
    2151                throw new IllegalArgumentException("Null 'stroke' argument.");
    2152            }
    2153            this.rangeMinorGridlineStroke = stroke;
    2154            fireChangeEvent();
    2155        }
    2156
    2157        /**
    2158         * Returns the paint for the minor grid lines (if any) plotted against the
    2159         * range axis.
    2160         *
    2161         * @return The paint (never <code>null</code>).
    2162         *
    2163         * @see #setRangeMinorGridlinePaint(Paint)
    2164         *
    2165         * @since 1.0.13
    2166         */
    2167        public Paint getRangeMinorGridlinePaint() {
    2168            return this.rangeMinorGridlinePaint;
    2169        }
    2170
    2171        /**
    2172         * Sets the paint for the minor grid lines plotted against the range axis
    2173         * and sends a {@link PlotChangeEvent} to all registered listeners.
    2174         *
    2175         * @param paint  the paint (<code>null</code> not permitted).
    2176         *
    2177         * @see #getRangeMinorGridlinePaint()
    2178         *
    2179         * @since 1.0.13
    2180         */
    2181        public void setRangeMinorGridlinePaint(Paint paint) {
    2182            if (paint == null) {
    2183                throw new IllegalArgumentException("Null 'paint' argument.");
    2184            }
    2185            this.rangeMinorGridlinePaint = paint;
    2186            fireChangeEvent();
    2187        }
    2188
    2189        /**
    2190         * Returns the fixed legend items, if any.
    2191         *
    2192         * @return The legend items (possibly <code>null</code>).
    2193         *
    2194         * @see #setFixedLegendItems(LegendItemCollection)
    2195         */
    2196        public LegendItemCollection getFixedLegendItems() {
    2197            return this.fixedLegendItems;
    2198        }
    2199
    2200        /**
    2201         * Sets the fixed legend items for the plot.  Leave this set to
    2202         * <code>null</code> if you prefer the legend items to be created
    2203         * automatically.
    2204         *
    2205         * @param items  the legend items (<code>null</code> permitted).
    2206         *
    2207         * @see #getFixedLegendItems()
    2208         */
    2209        public void setFixedLegendItems(LegendItemCollection items) {
    2210            this.fixedLegendItems = items;
    2211            fireChangeEvent();
    2212        }
    2213
    2214        /**
    2215         * Returns the legend items for the plot.  By default, this method creates
    2216         * a legend item for each series in each of the datasets.  You can change
    2217         * this behaviour by overriding this method.
    2218         *
    2219         * @return The legend items.
    2220         */
    2221        public LegendItemCollection getLegendItems() {
    2222            if (this.fixedLegendItems != null) {
    2223                return this.fixedLegendItems;
    2224            }
    2225            LegendItemCollection result = new LegendItemCollection();
    2226            // get the legend items for the datasets...
    2227            int count = this.datasets.size();
    2228            for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
    2229                CategoryDataset dataset = getDataset(datasetIndex);
    2230                if (dataset != null) {
    2231                    CategoryItemRenderer renderer = getRenderer(datasetIndex);
    2232                    if (renderer != null) {
    2233                        result.addAll(renderer.getLegendItems());
    2234                    }
    2235                }
    2236            }
    2237            return result;
    2238        }
    2239
    2240        /**
    2241         * Handles a 'click' on the plot by updating the anchor value.
    2242         *
    2243         * @param x  x-coordinate of the click (in Java2D space).
    2244         * @param y  y-coordinate of the click (in Java2D space).
    2245         * @param info  information about the plot's dimensions.
    2246         *
    2247         */
    2248        public void handleClick(int x, int y, PlotRenderingInfo info) {
    2249
    2250            Rectangle2D dataArea = info.getDataArea();
    2251            if (dataArea.contains(x, y)) {
    2252                // set the anchor value for the range axis...
    2253                double java2D = 0.0;
    2254                if (this.orientation == PlotOrientation.HORIZONTAL) {
    2255                    java2D = x;
    2256                }
    2257                else if (this.orientation == PlotOrientation.VERTICAL) {
    2258                    java2D = y;
    2259                }
    2260                RectangleEdge edge = Plot.resolveRangeAxisLocation(
    2261                        getRangeAxisLocation(), this.orientation);
    2262                double value = getRangeAxis().java2DToValue(
    2263                        java2D, info.getDataArea(), edge);
    2264                setAnchorValue(value);
    2265                setRangeCrosshairValue(value);
    2266            }
    2267
    2268        }
    2269
    2270        /**
    2271         * Zooms (in or out) on the plot's value axis.
    2272         * <p>
    2273         * If the value 0.0 is passed in as the zoom percent, the auto-range
    2274         * calculation for the axis is restored (which sets the range to include
    2275         * the minimum and maximum data values, thus displaying all the data).
    2276         *
    2277         * @param percent  the zoom amount.
    2278         */
    2279        public void zoom(double percent) {
    2280
    2281            if (percent > 0.0) {
    2282                double range = getRangeAxis().getRange().getLength();
    2283                double scaledRange = range * percent;
    2284                getRangeAxis().setRange(this.anchorValue - scaledRange / 2.0,
    2285                        this.anchorValue + scaledRange / 2.0);
    2286            }
    2287            else {
    2288                getRangeAxis().setAutoRange(true);
    2289            }
    2290
    2291        }
    2292
    2293        /**
    2294         * Receives notification of a change to an {@link Annotation} added to
    2295         * this plot.
    2296         *
    2297         * @param event  information about the event (not used here).
    2298         *
    2299         * @since 1.0.14
    2300         */
    2301        public void annotationChanged(AnnotationChangeEvent event) {
    2302            if (getParent() != null) {
    2303                getParent().annotationChanged(event);
    2304            }
    2305            else {
    2306                PlotChangeEvent e = new PlotChangeEvent(this);
    2307                notifyListeners(e);
    2308            }
    2309        }
    2310
    2311        /**
    2312         * Receives notification of a change to the plot's dataset.
    2313         * <P>
    2314         * The range axis bounds will be recalculated if necessary.
    2315         *
    2316         * @param event  information about the event (not used here).
    2317         */
    2318        public void datasetChanged(DatasetChangeEvent event) {
    2319
    2320            int count = this.rangeAxes.size();
    2321            for (int axisIndex = 0; axisIndex < count; axisIndex++) {
    2322                ValueAxis yAxis = getRangeAxis(axisIndex);
    2323                if (yAxis != null) {
    2324                    yAxis.configure();
    2325                }
    2326            }
    2327            if (getParent() != null) {
    2328                getParent().datasetChanged(event);
    2329            }
    2330            else {
    2331                PlotChangeEvent e = new PlotChangeEvent(this);
    2332                e.setType(ChartChangeEventType.DATASET_UPDATED);
    2333                notifyListeners(e);
    2334            }
    2335
    2336        }
    2337
    2338        /**
    2339         * Receives notification of a renderer change event.
    2340         *
    2341         * @param event  the event.
    2342         */
    2343        public void rendererChanged(RendererChangeEvent event) {
    2344            Plot parent = getParent();
    2345            if (parent != null) {
    2346                if (parent instanceof RendererChangeListener) {
    2347                    RendererChangeListener rcl = (RendererChangeListener) parent;
    2348                    rcl.rendererChanged(event);
    2349                }
    2350                else {
    2351                    // this should never happen with the existing code, but throw
    2352                    // an exception in case future changes make it possible...
    2353                    throw new RuntimeException(
    2354                        "The renderer has changed and I don't know what to do!");
    2355                }
    2356            }
    2357            else {
    2358                configureRangeAxes();
    2359                PlotChangeEvent e = new PlotChangeEvent(this);
    2360                notifyListeners(e);
    2361            }
    2362        }
    2363
    2364        /**
    2365         * Adds a marker for display (in the foreground) against the domain axis and
    2366         * sends a {@link PlotChangeEvent} to all registered listeners. Typically a
    2367         * marker will be drawn by the renderer as a line perpendicular to the
    2368         * domain axis, however this is entirely up to the renderer.
    2369         *
    2370         * @param marker  the marker (<code>null</code> not permitted).
    2371         *
    2372         * @see #removeDomainMarker(Marker)
    2373         */
    2374        public void addDomainMarker(CategoryMarker marker) {
    2375            addDomainMarker(marker, Layer.FOREGROUND);
    2376        }
    2377
    2378        /**
    2379         * Adds a marker for display against the domain axis and sends a
    2380         * {@link PlotChangeEvent} to all registered listeners.  Typically a marker
    2381         * will be drawn by the renderer as a line perpendicular to the domain
    2382         * axis, however this is entirely up to the renderer.
    2383         *
    2384         * @param marker  the marker (<code>null</code> not permitted).
    2385         * @param layer  the layer (foreground or background) (<code>null</code>
    2386         *               not permitted).
    2387         *
    2388         * @see #removeDomainMarker(Marker, Layer)
    2389         */
    2390        public void addDomainMarker(CategoryMarker marker, Layer layer) {
    2391            addDomainMarker(0, marker, layer);
    2392        }
    2393
    2394        /**
    2395         * Adds a marker for display by a particular renderer and sends a
    2396         * {@link PlotChangeEvent} to all registered listeners.
    2397         * <P>
    2398         * Typically a marker will be drawn by the renderer as a line perpendicular
    2399         * to a domain axis, however this is entirely up to the renderer.
    2400         *
    2401         * @param index  the renderer index.
    2402         * @param marker  the marker (<code>null</code> not permitted).
    2403         * @param layer  the layer (<code>null</code> not permitted).
    2404         *
    2405         * @see #removeDomainMarker(int, Marker, Layer)
    2406         */
    2407        public void addDomainMarker(int index, CategoryMarker marker, Layer layer) {
    2408            addDomainMarker(index, marker, layer, true);
    2409        }
    2410
    2411        /**
    2412         * Adds a marker for display by a particular renderer and, if requested,
    2413         * sends a {@link PlotChangeEvent} to all registered listeners.
    2414         * <P>
    2415         * Typically a marker will be drawn by the renderer as a line perpendicular
    2416         * to a domain axis, however this is entirely up to the renderer.
    2417         *
    2418         * @param index  the renderer index.
    2419         * @param marker  the marker (<code>null</code> not permitted).
    2420         * @param layer  the layer (<code>null</code> not permitted).
    2421         * @param notify  notify listeners?
    2422         *
    2423         * @since 1.0.10
    2424         *
    2425         * @see #removeDomainMarker(int, Marker, Layer, boolean)
    2426         */
    2427        public void addDomainMarker(int index, CategoryMarker marker, Layer layer,
    2428                boolean notify) {
    2429            if (marker == null) {
    2430                throw new IllegalArgumentException("Null 'marker' not permitted.");
    2431            }
    2432            if (layer == null) {
    2433                throw new IllegalArgumentException("Null 'layer' not permitted.");
    2434            }
    2435            Collection markers;
    2436            if (layer == Layer.FOREGROUND) {
    2437                markers = (Collection) this.foregroundDomainMarkers.get(
    2438                        new Integer(index));
    2439                if (markers == null) {
    2440                    markers = new java.util.ArrayList();
    2441                    this.foregroundDomainMarkers.put(new Integer(index), markers);
    2442                }
    2443                markers.add(marker);
    2444            }
    2445            else if (layer == Layer.BACKGROUND) {
    2446                markers = (Collection) this.backgroundDomainMarkers.get(
    2447                        new Integer(index));
    2448                if (markers == null) {
    2449                    markers = new java.util.ArrayList();
    2450                    this.backgroundDomainMarkers.put(new Integer(index), markers);
    2451                }
    2452                markers.add(marker);
    2453            }
    2454            marker.addChangeListener(this);
    2455            if (notify) {
    2456                fireChangeEvent();
    2457            }
    2458        }
    2459
    2460        /**
    2461         * Clears all the domain markers for the plot and sends a
    2462         * {@link PlotChangeEvent} to all registered listeners.
    2463         *
    2464         * @see #clearRangeMarkers()
    2465         */
    2466        public void clearDomainMarkers() {
    2467            if (this.backgroundDomainMarkers != null) {
    2468                Set keys = this.backgroundDomainMarkers.keySet();
    2469                Iterator iterator = keys.iterator();
    2470                while (iterator.hasNext()) {
    2471                    Integer key = (Integer) iterator.next();
    2472                    clearDomainMarkers(key.intValue());
    2473                }
    2474                this.backgroundDomainMarkers.clear();
    2475            }
    2476            if (this.foregroundDomainMarkers != null) {
    2477                Set keys = this.foregroundDomainMarkers.keySet();
    2478                Iterator iterator = keys.iterator();
    2479                while (iterator.hasNext()) {
    2480                    Integer key = (Integer) iterator.next();
    2481                    clearDomainMarkers(key.intValue());
    2482                }
    2483                this.foregroundDomainMarkers.clear();
    2484            }
    2485            fireChangeEvent();
    2486        }
    2487
    2488        /**
    2489         * Returns the list of domain markers (read only) for the specified layer.
    2490         *
    2491         * @param layer  the layer (foreground or background).
    2492         *
    2493         * @return The list of domain markers.
    2494         */
    2495        public Collection getDomainMarkers(Layer layer) {
    2496            return getDomainMarkers(0, layer);
    2497        }
    2498
    2499        /**
    2500         * Returns a collection of domain markers for a particular renderer and
    2501         * layer.
    2502         *
    2503         * @param index  the renderer index.
    2504         * @param layer  the layer.
    2505         *
    2506         * @return A collection of markers (possibly <code>null</code>).
    2507         */
    2508        public Collection getDomainMarkers(int index, Layer layer) {
    2509            Collection result = null;
    2510            Integer key = new Integer(index);
    2511            if (layer == Layer.FOREGROUND) {
    2512                result = (Collection) this.foregroundDomainMarkers.get(key);
    2513            }
    2514            else if (layer == Layer.BACKGROUND) {
    2515                result = (Collection) this.backgroundDomainMarkers.get(key);
    2516            }
    2517            if (result != null) {
    2518                result = Collections.unmodifiableCollection(result);
    2519            }
    2520            return result;
    2521        }
    2522
    2523        /**
    2524         * Clears all the domain markers for the specified renderer.
    2525         *
    2526         * @param index  the renderer index.
    2527         *
    2528         * @see #clearRangeMarkers(int)
    2529         */
    2530        public void clearDomainMarkers(int index) {
    2531            Integer key = new Integer(index);
    2532            if (this.backgroundDomainMarkers != null) {
    2533                Collection markers
    2534                    = (Collection) this.backgroundDomainMarkers.get(key);
    2535                if (markers != null) {
    2536                    Iterator iterator = markers.iterator();
    2537                    while (iterator.hasNext()) {
    2538                        Marker m = (Marker) iterator.next();
    2539                        m.removeChangeListener(this);
    2540                    }
    2541                    markers.clear();
    2542                }
    2543            }
    2544            if (this.foregroundDomainMarkers != null) {
    2545                Collection markers
    2546                    = (Collection) this.foregroundDomainMarkers.get(key);
    2547                if (markers != null) {
    2548                    Iterator iterator = markers.iterator();
    2549                    while (iterator.hasNext()) {
    2550                        Marker m = (Marker) iterator.next();
    2551                        m.removeChangeListener(this);
    2552                    }
    2553                    markers.clear();
    2554                }
    2555            }
    2556            fireChangeEvent();
    2557        }
    2558
    2559        /**
    2560         * Removes a marker for the domain axis and sends a {@link PlotChangeEvent}
    2561         * to all registered listeners.
    2562         *
    2563         * @param marker  the marker.
    2564         *
    2565         * @return A boolean indicating whether or not the marker was actually
    2566         *         removed.
    2567         *
    2568         * @since 1.0.7
    2569         */
    2570        public boolean removeDomainMarker(Marker marker) {
    2571            return removeDomainMarker(marker, Layer.FOREGROUND);
    2572        }
    2573
    2574        /**
    2575         * Removes a marker for the domain axis in the specified layer and sends a
    2576         * {@link PlotChangeEvent} to all registered listeners.
    2577         *
    2578         * @param marker the marker (<code>null</code> not permitted).
    2579         * @param layer the layer (foreground or background).
    2580         *
    2581         * @return A boolean indicating whether or not the marker was actually
    2582         *         removed.
    2583         *
    2584         * @since 1.0.7
    2585         */
    2586        public boolean removeDomainMarker(Marker marker, Layer layer) {
    2587            return removeDomainMarker(0, marker, layer);
    2588        }
    2589
    2590        /**
    2591         * Removes a marker for a specific dataset/renderer and sends a
    2592         * {@link PlotChangeEvent} to all registered listeners.
    2593         *
    2594         * @param index the dataset/renderer index.
    2595         * @param marker the marker.
    2596         * @param layer the layer (foreground or background).
    2597         *
    2598         * @return A boolean indicating whether or not the marker was actually
    2599         *         removed.
    2600         *
    2601         * @since 1.0.7
    2602         */
    2603        public boolean removeDomainMarker(int index, Marker marker, Layer layer) {
    2604            return removeDomainMarker(index, marker, layer, true);
    2605        }
    2606
    2607        /**
    2608         * Removes a marker for a specific dataset/renderer and, if requested,
    2609         * sends a {@link PlotChangeEvent} to all registered listeners.
    2610         *
    2611         * @param index the dataset/renderer index.
    2612         * @param marker the marker.
    2613         * @param layer the layer (foreground or background).
    2614         * @param notify  notify listeners?
    2615         *
    2616         * @return A boolean indicating whether or not the marker was actually
    2617         *         removed.
    2618         *
    2619         * @since 1.0.10
    2620         */
    2621        public boolean removeDomainMarker(int index, Marker marker, Layer layer,
    2622                boolean notify) {
    2623            ArrayList markers;
    2624            if (layer == Layer.FOREGROUND) {
    2625                markers = (ArrayList) this.foregroundDomainMarkers.get(new Integer(
    2626                        index));
    2627            }
    2628            else {
    2629                markers = (ArrayList) this.backgroundDomainMarkers.get(new Integer(
    2630                        index));
    2631            }
    2632            if (markers == null) {
    2633                return false;
    2634            }
    2635            boolean removed = markers.remove(marker);
    2636            if (removed && notify) {
    2637                fireChangeEvent();
    2638            }
    2639            return removed;
    2640        }
    2641
    2642        /**
    2643         * Adds a marker for display (in the foreground) against the range axis and
    2644         * sends a {@link PlotChangeEvent} to all registered listeners. Typically a
    2645         * marker will be drawn by the renderer as a line perpendicular to the
    2646         * range axis, however this is entirely up to the renderer.
    2647         *
    2648         * @param marker  the marker (<code>null</code> not permitted).
    2649         *
    2650         * @see #removeRangeMarker(Marker)
    2651         */
    2652        public void addRangeMarker(Marker marker) {
    2653            addRangeMarker(marker, Layer.FOREGROUND);
    2654        }
    2655
    2656        /**
    2657         * Adds a marker for display against the range axis and sends a
    2658         * {@link PlotChangeEvent} to all registered listeners.  Typically a marker
    2659         * will be drawn by the renderer as a line perpendicular to the range axis,
    2660         * however this is entirely up to the renderer.
    2661         *
    2662         * @param marker  the marker (<code>null</code> not permitted).
    2663         * @param layer  the layer (foreground or background) (<code>null</code>
    2664         *               not permitted).
    2665         *
    2666         * @see #removeRangeMarker(Marker, Layer)
    2667         */
    2668        public void addRangeMarker(Marker marker, Layer layer) {
    2669            addRangeMarker(0, marker, layer);
    2670        }
    2671
    2672        /**
    2673         * Adds a marker for display by a particular renderer and sends a
    2674         * {@link PlotChangeEvent} to all registered listeners.
    2675         * <P>
    2676         * Typically a marker will be drawn by the renderer as a line perpendicular
    2677         * to a range axis, however this is entirely up to the renderer.
    2678         *
    2679         * @param index  the renderer index.
    2680         * @param marker  the marker.
    2681         * @param layer  the layer.
    2682         *
    2683         * @see #removeRangeMarker(int, Marker, Layer)
    2684         */
    2685        public void addRangeMarker(int index, Marker marker, Layer layer) {
    2686            addRangeMarker(index, marker, layer, true);
    2687        }
    2688
    2689        /**
    2690         * Adds a marker for display by a particular renderer and sends a
    2691         * {@link PlotChangeEvent} to all registered listeners.
    2692         * <P>
    2693         * Typically a marker will be drawn by the renderer as a line perpendicular
    2694         * to a range axis, however this is entirely up to the renderer.
    2695         *
    2696         * @param index  the renderer index.
    2697         * @param marker  the marker.
    2698         * @param layer  the layer.
    2699         * @param notify  notify listeners?
    2700         *
    2701         * @since 1.0.10
    2702         *
    2703         * @see #removeRangeMarker(int, Marker, Layer, boolean)
    2704         */
    2705        public void addRangeMarker(int index, Marker marker, Layer layer,
    2706                boolean notify) {
    2707            Collection markers;
    2708            if (layer == Layer.FOREGROUND) {
    2709                markers = (Collection) this.foregroundRangeMarkers.get(
    2710                        new Integer(index));
    2711                if (markers == null) {
    2712                    markers = new java.util.ArrayList();
    2713                    this.foregroundRangeMarkers.put(new Integer(index), markers);
    2714                }
    2715                markers.add(marker);
    2716            }
    2717            else if (layer == Layer.BACKGROUND) {
    2718                markers = (Collection) this.backgroundRangeMarkers.get(
    2719                        new Integer(index));
    2720                if (markers == null) {
    2721                    markers = new java.util.ArrayList();
    2722                    this.backgroundRangeMarkers.put(new Integer(index), markers);
    2723                }
    2724                markers.add(marker);
    2725            }
    2726            marker.addChangeListener(this);
    2727            if (notify) {
    2728                fireChangeEvent();
    2729            }
    2730        }
    2731
    2732        /**
    2733         * Clears all the range markers for the plot and sends a
    2734         * {@link PlotChangeEvent} to all registered listeners.
    2735         *
    2736         * @see #clearDomainMarkers()
    2737         */
    2738        public void clearRangeMarkers() {
    2739            if (this.backgroundRangeMarkers != null) {
    2740                Set keys = this.backgroundRangeMarkers.keySet();
    2741                Iterator iterator = keys.iterator();
    2742                while (iterator.hasNext()) {
    2743                    Integer key = (Integer) iterator.next();
    2744                    clearRangeMarkers(key.intValue());
    2745                }
    2746                this.backgroundRangeMarkers.clear();
    2747            }
    2748            if (this.foregroundRangeMarkers != null) {
    2749                Set keys = this.foregroundRangeMarkers.keySet();
    2750                Iterator iterator = keys.iterator();
    2751                while (iterator.hasNext()) {
    2752                    Integer key = (Integer) iterator.next();
    2753                    clearRangeMarkers(key.intValue());
    2754                }
    2755                this.foregroundRangeMarkers.clear();
    2756            }
    2757            fireChangeEvent();
    2758        }
    2759
    2760        /**
    2761         * Returns the list of range markers (read only) for the specified layer.
    2762         *
    2763         * @param layer  the layer (foreground or background).
    2764         *
    2765         * @return The list of range markers.
    2766         *
    2767         * @see #getRangeMarkers(int, Layer)
    2768         */
    2769        public Collection getRangeMarkers(Layer layer) {
    2770            return getRangeMarkers(0, layer);
    2771        }
    2772
    2773        /**
    2774         * Returns a collection of range markers for a particular renderer and
    2775         * layer.
    2776         *
    2777         * @param index  the renderer index.
    2778         * @param layer  the layer.
    2779         *
    2780         * @return A collection of markers (possibly <code>null</code>).
    2781         */
    2782        public Collection getRangeMarkers(int index, Layer layer) {
    2783            Collection result = null;
    2784            Integer key = new Integer(index);
    2785            if (layer == Layer.FOREGROUND) {
    2786                result = (Collection) this.foregroundRangeMarkers.get(key);
    2787            }
    2788            else if (layer == Layer.BACKGROUND) {
    2789                result = (Collection) this.backgroundRangeMarkers.get(key);
    2790            }
    2791            if (result != null) {
    2792                result = Collections.unmodifiableCollection(result);
    2793            }
    2794            return result;
    2795        }
    2796
    2797        /**
    2798         * Clears all the range markers for the specified renderer.
    2799         *
    2800         * @param index  the renderer index.
    2801         *
    2802         * @see #clearDomainMarkers(int)
    2803         */
    2804        public void clearRangeMarkers(int index) {
    2805            Integer key = new Integer(index);
    2806            if (this.backgroundRangeMarkers != null) {
    2807                Collection markers
    2808                    = (Collection) this.backgroundRangeMarkers.get(key);
    2809                if (markers != null) {
    2810                    Iterator iterator = markers.iterator();
    2811                    while (iterator.hasNext()) {
    2812                        Marker m = (Marker) iterator.next();
    2813                        m.removeChangeListener(this);
    2814                    }
    2815                    markers.clear();
    2816                }
    2817            }
    2818            if (this.foregroundRangeMarkers != null) {
    2819                Collection markers
    2820                    = (Collection) this.foregroundRangeMarkers.get(key);
    2821                if (markers != null) {
    2822                    Iterator iterator = markers.iterator();
    2823                    while (iterator.hasNext()) {
    2824                        Marker m = (Marker) iterator.next();
    2825                        m.removeChangeListener(this);
    2826                    }
    2827                    markers.clear();
    2828                }
    2829            }
    2830            fireChangeEvent();
    2831        }
    2832
    2833        /**
    2834         * Removes a marker for the range axis and sends a {@link PlotChangeEvent}
    2835         * to all registered listeners.
    2836         *
    2837         * @param marker the marker.
    2838         *
    2839         * @return A boolean indicating whether or not the marker was actually
    2840         *         removed.
    2841         *
    2842         * @since 1.0.7
    2843         *
    2844         * @see #addRangeMarker(Marker)
    2845         */
    2846        public boolean removeRangeMarker(Marker marker) {
    2847            return removeRangeMarker(marker, Layer.FOREGROUND);
    2848        }
    2849
    2850        /**
    2851         * Removes a marker for the range axis in the specified layer and sends a
    2852         * {@link PlotChangeEvent} to all registered listeners.
    2853         *
    2854         * @param marker the marker (<code>null</code> not permitted).
    2855         * @param layer the layer (foreground or background).
    2856         *
    2857         * @return A boolean indicating whether or not the marker was actually
    2858         *         removed.
    2859         *
    2860         * @since 1.0.7
    2861         *
    2862         * @see #addRangeMarker(Marker, Layer)
    2863         */
    2864        public boolean removeRangeMarker(Marker marker, Layer layer) {
    2865            return removeRangeMarker(0, marker, layer);
    2866        }
    2867
    2868        /**
    2869         * Removes a marker for a specific dataset/renderer and sends a
    2870         * {@link PlotChangeEvent} to all registered listeners.
    2871         *
    2872         * @param index the dataset/renderer index.
    2873         * @param marker the marker.
    2874         * @param layer the layer (foreground or background).
    2875         *
    2876         * @return A boolean indicating whether or not the marker was actually
    2877         *         removed.
    2878         *
    2879         * @since 1.0.7
    2880         *
    2881         * @see #addRangeMarker(int, Marker, Layer)
    2882         */
    2883        public boolean removeRangeMarker(int index, Marker marker, Layer layer) {
    2884            return removeRangeMarker(index, marker, layer, true);
    2885        }
    2886
    2887        /**
    2888         * Removes a marker for a specific dataset/renderer and sends a
    2889         * {@link PlotChangeEvent} to all registered listeners.
    2890         *
    2891         * @param index  the dataset/renderer index.
    2892         * @param marker  the marker.
    2893         * @param layer  the layer (foreground or background).
    2894         * @param notify  notify listeners.
    2895         *
    2896         * @return A boolean indicating whether or not the marker was actually
    2897         *         removed.
    2898         *
    2899         * @since 1.0.10
    2900         *
    2901         * @see #addRangeMarker(int, Marker, Layer, boolean)
    2902         */
    2903        public boolean removeRangeMarker(int index, Marker marker, Layer layer,
    2904                boolean notify) {
    2905            if (marker == null) {
    2906                throw new IllegalArgumentException("Null 'marker' argument.");
    2907            }
    2908            ArrayList markers;
    2909            if (layer == Layer.FOREGROUND) {
    2910                markers = (ArrayList) this.foregroundRangeMarkers.get(new Integer(
    2911                        index));
    2912            }
    2913            else {
    2914                markers = (ArrayList) this.backgroundRangeMarkers.get(new Integer(
    2915                        index));
    2916            }
    2917            if (markers == null) {
    2918                return false;
    2919            }
    2920            boolean removed = markers.remove(marker);
    2921            if (removed && notify) {
    2922                fireChangeEvent();
    2923            }
    2924            return removed;
    2925        }
    2926
    2927        /**
    2928         * Returns the flag that controls whether or not the domain crosshair is
    2929         * displayed by the plot.
    2930         *
    2931         * @return A boolean.
    2932         *
    2933         * @since 1.0.11
    2934         *
    2935         * @see #setDomainCrosshairVisible(boolean)
    2936         */
    2937        public boolean isDomainCrosshairVisible() {
    2938            return this.domainCrosshairVisible;
    2939        }
    2940
    2941        /**
    2942         * Sets the flag that controls whether or not the domain crosshair is
    2943         * displayed by the plot, and sends a {@link PlotChangeEvent} to all
    2944         * registered listeners.
    2945         *
    2946         * @param flag  the new flag value.
    2947         *
    2948         * @since 1.0.11
    2949         *
    2950         * @see #isDomainCrosshairVisible()
    2951         * @see #setRangeCrosshairVisible(boolean)
    2952         */
    2953        public void setDomainCrosshairVisible(boolean flag) {
    2954            if (this.domainCrosshairVisible != flag) {
    2955                this.domainCrosshairVisible = flag;
    2956                fireChangeEvent();
    2957            }
    2958        }
    2959
    2960        /**
    2961         * Returns the row key for the domain crosshair.
    2962         *
    2963         * @return The row key.
    2964         *
    2965         * @since 1.0.11
    2966         */
    2967        public Comparable getDomainCrosshairRowKey() {
    2968            return this.domainCrosshairRowKey;
    2969        }
    2970
    2971        /**
    2972         * Sets the row key for the domain crosshair and sends a
    2973         * {PlotChangeEvent} to all registered listeners.
    2974         *
    2975         * @param key  the key.
    2976         *
    2977         * @since 1.0.11
    2978         */
    2979        public void setDomainCrosshairRowKey(Comparable key) {
    2980            setDomainCrosshairRowKey(key, true);
    2981        }
    2982
    2983        /**
    2984         * Sets the row key for the domain crosshair and, if requested, sends a
    2985         * {PlotChangeEvent} to all registered listeners.
    2986         *
    2987         * @param key  the key.
    2988         * @param notify  notify listeners?
    2989         *
    2990         * @since 1.0.11
    2991         */
    2992        public void setDomainCrosshairRowKey(Comparable key, boolean notify) {
    2993            this.domainCrosshairRowKey = key;
    2994            if (notify) {
    2995                fireChangeEvent();
    2996            }
    2997        }
    2998
    2999        /**
    3000         * Returns the column key for the domain crosshair.
    3001         *
    3002         * @return The column key.
    3003         *
    3004         * @since 1.0.11
    3005         */
    3006        public Comparable getDomainCrosshairColumnKey() {
    3007            return this.domainCrosshairColumnKey;
    3008        }
    3009
    3010        /**
    3011         * Sets the column key for the domain crosshair and sends
    3012         * a {@link PlotChangeEvent} to all registered listeners.
    3013         *
    3014         * @param key  the key.
    3015         *
    3016         * @since 1.0.11
    3017         */
    3018        public void setDomainCrosshairColumnKey(Comparable key) {
    3019            setDomainCrosshairColumnKey(key, true);
    3020        }
    3021
    3022        /**
    3023         * Sets the column key for the domain crosshair and, if requested, sends
    3024         * a {@link PlotChangeEvent} to all registered listeners.
    3025         *
    3026         * @param key  the key.
    3027         * @param notify  notify listeners?
    3028         *
    3029         * @since 1.0.11
    3030         */
    3031        public void setDomainCrosshairColumnKey(Comparable key, boolean notify) {
    3032            this.domainCrosshairColumnKey = key;
    3033            if (notify) {
    3034                fireChangeEvent();
    3035            }
    3036        }
    3037
    3038        /**
    3039         * Returns the dataset index for the crosshair.
    3040         *
    3041         * @return The dataset index.
    3042         *
    3043         * @since 1.0.11
    3044         */
    3045        public int getCrosshairDatasetIndex() {
    3046            return this.crosshairDatasetIndex;
    3047        }
    3048
    3049        /**
    3050         * Sets the dataset index for the crosshair and sends a
    3051         * {@link PlotChangeEvent} to all registered listeners.
    3052         *
    3053         * @param index  the index.
    3054         *
    3055         * @since 1.0.11
    3056         */
    3057        public void setCrosshairDatasetIndex(int index) {
    3058            setCrosshairDatasetIndex(index, true);
    3059        }
    3060
    3061        /**
    3062         * Sets the dataset index for the crosshair and, if requested, sends a
    3063         * {@link PlotChangeEvent} to all registered listeners.
    3064         *
    3065         * @param index  the index.
    3066         * @param notify  notify listeners?
    3067         *
    3068         * @since 1.0.11
    3069         */
    3070        public void setCrosshairDatasetIndex(int index, boolean notify) {
    3071            this.crosshairDatasetIndex = index;
    3072            if (notify) {
    3073                fireChangeEvent();
    3074            }
    3075        }
    3076
    3077        /**
    3078         * Returns the paint used to draw the domain crosshair.
    3079         *
    3080         * @return The paint (never <code>null</code>).
    3081         *
    3082         * @since 1.0.11
    3083         *
    3084         * @see #setDomainCrosshairPaint(Paint)
    3085         * @see #getDomainCrosshairStroke()
    3086         */
    3087        public Paint getDomainCrosshairPaint() {
    3088            return this.domainCrosshairPaint;
    3089        }
    3090
    3091        /**
    3092         * Sets the paint used to draw the domain crosshair.
    3093         *
    3094         * @param paint  the paint (<code>null</code> not permitted).
    3095         *
    3096         * @since 1.0.11
    3097         *
    3098         * @see #getDomainCrosshairPaint()
    3099         */
    3100        public void setDomainCrosshairPaint(Paint paint) {
    3101            if (paint == null) {
    3102                throw new IllegalArgumentException("Null 'paint' argument.");
    3103            }
    3104            this.domainCrosshairPaint = paint;
    3105            fireChangeEvent();
    3106        }
    3107
    3108        /**
    3109         * Returns the stroke used to draw the domain crosshair.
    3110         *
    3111         * @return The stroke (never <code>null</code>).
    3112         *
    3113         * @since 1.0.11
    3114         *
    3115         * @see #setDomainCrosshairStroke(Stroke)
    3116         * @see #getDomainCrosshairPaint()
    3117         */
    3118        public Stroke getDomainCrosshairStroke() {
    3119            return this.domainCrosshairStroke;
    3120        }
    3121
    3122        /**
    3123         * Sets the stroke used to draw the domain crosshair, and sends a
    3124         * {@link PlotChangeEvent} to all registered listeners.
    3125         *
    3126         * @param stroke  the stroke (<code>null</code> not permitted).
    3127         *
    3128         * @since 1.0.11
    3129         *
    3130         * @see #getDomainCrosshairStroke()
    3131         */
    3132        public void setDomainCrosshairStroke(Stroke stroke) {
    3133            if (stroke == null) {
    3134                throw new IllegalArgumentException("Null 'stroke' argument.");
    3135            }
    3136            this.domainCrosshairStroke = stroke;
    3137        }
    3138
    3139        /**
    3140         * Returns a flag indicating whether or not the range crosshair is visible.
    3141         *
    3142         * @return The flag.
    3143         *
    3144         * @see #setRangeCrosshairVisible(boolean)
    3145         */
    3146        public boolean isRangeCrosshairVisible() {
    3147            return this.rangeCrosshairVisible;
    3148        }
    3149
    3150        /**
    3151         * Sets the flag indicating whether or not the range crosshair is visible.
    3152         *
    3153         * @param flag  the new value of the flag.
    3154         *
    3155         * @see #isRangeCrosshairVisible()
    3156         */
    3157        public void setRangeCrosshairVisible(boolean flag) {
    3158            if (this.rangeCrosshairVisible != flag) {
    3159                this.rangeCrosshairVisible = flag;
    3160                fireChangeEvent();
    3161            }
    3162        }
    3163
    3164        /**
    3165         * Returns a flag indicating whether or not the crosshair should "lock-on"
    3166         * to actual data values.
    3167         *
    3168         * @return The flag.
    3169         *
    3170         * @see #setRangeCrosshairLockedOnData(boolean)
    3171         */
    3172        public boolean isRangeCrosshairLockedOnData() {
    3173            return this.rangeCrosshairLockedOnData;
    3174        }
    3175
    3176        /**
    3177         * Sets the flag indicating whether or not the range crosshair should
    3178         * "lock-on" to actual data values, and sends a {@link PlotChangeEvent}
    3179         * to all registered listeners.
    3180         *
    3181         * @param flag  the flag.
    3182         *
    3183         * @see #isRangeCrosshairLockedOnData()
    3184         */
    3185        public void setRangeCrosshairLockedOnData(boolean flag) {
    3186            if (this.rangeCrosshairLockedOnData != flag) {
    3187                this.rangeCrosshairLockedOnData = flag;
    3188                fireChangeEvent();
    3189            }
    3190        }
    3191
    3192        /**
    3193         * Returns the range crosshair value.
    3194         *
    3195         * @return The value.
    3196         *
    3197         * @see #setRangeCrosshairValue(double)
    3198         */
    3199        public double getRangeCrosshairValue() {
    3200            return this.rangeCrosshairValue;
    3201        }
    3202
    3203        /**
    3204         * Sets the range crosshair value and, if the crosshair is visible, sends
    3205         * a {@link PlotChangeEvent} to all registered listeners.
    3206         *
    3207         * @param value  the new value.
    3208         *
    3209         * @see #getRangeCrosshairValue()
    3210         */
    3211        public void setRangeCrosshairValue(double value) {
    3212            setRangeCrosshairValue(value, true);
    3213        }
    3214
    3215        /**
    3216         * Sets the range crosshair value and, if requested, sends a
    3217         * {@link PlotChangeEvent} to all registered listeners (but only if the
    3218         * crosshair is visible).
    3219         *
    3220         * @param value  the new value.
    3221         * @param notify  a flag that controls whether or not listeners are
    3222         *                notified.
    3223         *
    3224         * @see #getRangeCrosshairValue()
    3225         */
    3226        public void setRangeCrosshairValue(double value, boolean notify) {
    3227            this.rangeCrosshairValue = value;
    3228            if (isRangeCrosshairVisible() && notify) {
    3229                fireChangeEvent();
    3230            }
    3231        }
    3232
    3233        /**
    3234         * Returns the pen-style (<code>Stroke</code>) used to draw the crosshair
    3235         * (if visible).
    3236         *
    3237         * @return The crosshair stroke (never <code>null</code>).
    3238         *
    3239         * @see #setRangeCrosshairStroke(Stroke)
    3240         * @see #isRangeCrosshairVisible()
    3241         * @see #getRangeCrosshairPaint()
    3242         */
    3243        public Stroke getRangeCrosshairStroke() {
    3244            return this.rangeCrosshairStroke;
    3245        }
    3246
    3247        /**
    3248         * Sets the pen-style (<code>Stroke</code>) used to draw the range
    3249         * crosshair (if visible), and sends a {@link PlotChangeEvent} to all
    3250         * registered listeners.
    3251         *
    3252         * @param stroke  the new crosshair stroke (<code>null</code> not
    3253         *         permitted).
    3254         *
    3255         * @see #getRangeCrosshairStroke()
    3256         */
    3257        public void setRangeCrosshairStroke(Stroke stroke) {
    3258            if (stroke == null) {
    3259                throw new IllegalArgumentException("Null 'stroke' argument.");
    3260            }
    3261            this.rangeCrosshairStroke = stroke;
    3262            fireChangeEvent();
    3263        }
    3264
    3265        /**
    3266         * Returns the paint used to draw the range crosshair.
    3267         *
    3268         * @return The paint (never <code>null</code>).
    3269         *
    3270         * @see #setRangeCrosshairPaint(Paint)
    3271         * @see #isRangeCrosshairVisible()
    3272         * @see #getRangeCrosshairStroke()
    3273         */
    3274        public Paint getRangeCrosshairPaint() {
    3275            return this.rangeCrosshairPaint;
    3276        }
    3277
    3278        /**
    3279         * Sets the paint used to draw the range crosshair (if visible) and
    3280         * sends a {@link PlotChangeEvent} to all registered listeners.
    3281         *
    3282         * @param paint  the paint (<code>null</code> not permitted).
    3283         *
    3284         * @see #getRangeCrosshairPaint()
    3285         */
    3286        public void setRangeCrosshairPaint(Paint paint) {
    3287            if (paint == null) {
    3288                throw new IllegalArgumentException("Null 'paint' argument.");
    3289            }
    3290            this.rangeCrosshairPaint = paint;
    3291            fireChangeEvent();
    3292        }
    3293
    3294        /**
    3295         * Returns the list of annotations.
    3296         *
    3297         * @return The list of annotations (never <code>null</code>).
    3298         *
    3299         * @see #addAnnotation(CategoryAnnotation)
    3300         * @see #clearAnnotations()
    3301         */
    3302        public List getAnnotations() {
    3303            return this.annotations;
    3304        }
    3305
    3306        /**
    3307         * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to all
    3308         * registered listeners.
    3309         *
    3310         * @param annotation  the annotation (<code>null</code> not permitted).
    3311         *
    3312         * @see #removeAnnotation(CategoryAnnotation)
    3313         */
    3314        public void addAnnotation(CategoryAnnotation annotation) {
    3315            addAnnotation(annotation, true);
    3316        }
    3317
    3318        /**
    3319         * Adds an annotation to the plot and, if requested, sends a
    3320         * {@link PlotChangeEvent} to all registered listeners.
    3321         *
    3322         * @param annotation  the annotation (<code>null</code> not permitted).
    3323         * @param notify  notify listeners?
    3324         *
    3325         * @since 1.0.10
    3326         */
    3327        public void addAnnotation(CategoryAnnotation annotation, boolean notify) {
    3328            if (annotation == null) {
    3329                throw new IllegalArgumentException("Null 'annotation' argument.");
    3330            }
    3331            this.annotations.add(annotation);
    3332            annotation.addChangeListener(this);
    3333            if (notify) {
    3334                fireChangeEvent();
    3335            }
    3336        }
    3337
    3338        /**
    3339         * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
    3340         * to all registered listeners.
    3341         *
    3342         * @param annotation  the annotation (<code>null</code> not permitted).
    3343         *
    3344         * @return A boolean (indicates whether or not the annotation was removed).
    3345         *
    3346         * @see #addAnnotation(CategoryAnnotation)
    3347         */
    3348        public boolean removeAnnotation(CategoryAnnotation annotation) {
    3349            return removeAnnotation(annotation, true);
    3350        }
    3351
    3352        /**
    3353         * Removes an annotation from the plot and, if requested, sends a
    3354         * {@link PlotChangeEvent} to all registered listeners.
    3355         *
    3356         * @param annotation  the annotation (<code>null</code> not permitted).
    3357         * @param notify  notify listeners?
    3358         *
    3359         * @return A boolean (indicates whether or not the annotation was removed).
    3360         *
    3361         * @since 1.0.10
    3362         */
    3363        public boolean removeAnnotation(CategoryAnnotation annotation,
    3364                boolean notify) {
    3365            if (annotation == null) {
    3366                throw new IllegalArgumentException("Null 'annotation' argument.");
    3367            }
    3368            boolean removed = this.annotations.remove(annotation);
    3369            annotation.removeChangeListener(this);
    3370            if (removed && notify) {
    3371                fireChangeEvent();
    3372            }
    3373            return removed;
    3374        }
    3375
    3376        /**
    3377         * Clears all the annotations and sends a {@link PlotChangeEvent} to all
    3378         * registered listeners.
    3379         */
    3380        public void clearAnnotations() {
    3381            for(int i = 0; i < this.annotations.size(); i++) {
    3382                CategoryAnnotation annotation
    3383                        = (CategoryAnnotation) this.annotations.get(i);
    3384                annotation.removeChangeListener(this);
    3385            }
    3386            this.annotations.clear();
    3387            fireChangeEvent();
    3388        }
    3389
    3390        /**
    3391         * Returns the shadow generator for the plot, if any.
    3392         *
    3393         * @return The shadow generator (possibly <code>null</code>).
    3394         *
    3395         * @since 1.0.14
    3396         */
    3397        public ShadowGenerator getShadowGenerator() {
    3398            return this.shadowGenerator;
    3399        }
    3400
    3401        /**
    3402         * Sets the shadow generator for the plot and sends a
    3403         * {@link PlotChangeEvent} to all registered listeners.
    3404         *
    3405         * @param generator  the generator (<code>null</code> permitted).
    3406         *
    3407         * @since 1.0.14
    3408         */
    3409        public void setShadowGenerator(ShadowGenerator generator) {
    3410            this.shadowGenerator = generator;
    3411            fireChangeEvent();
    3412        }
    3413
    3414        /**
    3415         * Calculates the space required for the domain axis/axes.
    3416         *
    3417         * @param g2  the graphics device.
    3418         * @param plotArea  the plot area.
    3419         * @param space  a carrier for the result (<code>null</code> permitted).
    3420         *
    3421         * @return The required space.
    3422         */
    3423        protected AxisSpace calculateDomainAxisSpace(Graphics2D g2,
    3424                                                     Rectangle2D plotArea,
    3425                                                     AxisSpace space) {
    3426
    3427            if (space == null) {
    3428                space = new AxisSpace();
    3429            }
    3430
    3431            // reserve some space for the domain axis...
    3432            if (this.fixedDomainAxisSpace != null) {
    3433                if (this.orientation == PlotOrientation.HORIZONTAL) {
    3434                    space.ensureAtLeast(
    3435                        this.fixedDomainAxisSpace.getLeft(), RectangleEdge.LEFT);
    3436                    space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(),
    3437                            RectangleEdge.RIGHT);
    3438                }
    3439                else if (this.orientation == PlotOrientation.VERTICAL) {
    3440                    space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(),
    3441                            RectangleEdge.TOP);
    3442                    space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(),
    3443                            RectangleEdge.BOTTOM);
    3444                }
    3445            }
    3446            else {
    3447                // reserve space for the primary domain axis...
    3448                RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
    3449                        getDomainAxisLocation(), this.orientation);
    3450                if (this.drawSharedDomainAxis) {
    3451                    space = getDomainAxis().reserveSpace(g2, this, plotArea,
    3452                            domainEdge, space);
    3453                }
    3454
    3455                // reserve space for any domain axes...
    3456                for (int i = 0; i < this.domainAxes.size(); i++) {
    3457                    Axis xAxis = (Axis) this.domainAxes.get(i);
    3458                    if (xAxis != null) {
    3459                        RectangleEdge edge = getDomainAxisEdge(i);
    3460                        space = xAxis.reserveSpace(g2, this, plotArea, edge, space);
    3461                    }
    3462                }
    3463            }
    3464
    3465            return space;
    3466
    3467        }
    3468
    3469        /**
    3470         * Calculates the space required for the range axis/axes.
    3471         *
    3472         * @param g2  the graphics device.
    3473         * @param plotArea  the plot area.
    3474         * @param space  a carrier for the result (<code>null</code> permitted).
    3475         *
    3476         * @return The required space.
    3477         */
    3478        protected AxisSpace calculateRangeAxisSpace(Graphics2D g2,
    3479                                                    Rectangle2D plotArea,
    3480                                                    AxisSpace space) {
    3481
    3482            if (space == null) {
    3483                space = new AxisSpace();
    3484            }
    3485
    3486            // reserve some space for the range axis...
    3487            if (this.fixedRangeAxisSpace != null) {
    3488                if (this.orientation == PlotOrientation.HORIZONTAL) {
    3489                    space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(),
    3490                            RectangleEdge.TOP);
    3491                    space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(),
    3492                            RectangleEdge.BOTTOM);
    3493                }
    3494                else if (this.orientation == PlotOrientation.VERTICAL) {
    3495                    space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(),
    3496                            RectangleEdge.LEFT);
    3497                    space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(),
    3498                            RectangleEdge.RIGHT);
    3499                }
    3500            }
    3501            else {
    3502                // reserve space for the range axes (if any)...
    3503                for (int i = 0; i < this.rangeAxes.size(); i++) {
    3504                    Axis yAxis = (Axis) this.rangeAxes.get(i);
    3505                    if (yAxis != null) {
    3506                        RectangleEdge edge = getRangeAxisEdge(i);
    3507                        space = yAxis.reserveSpace(g2, this, plotArea, edge, space);
    3508                    }
    3509                }
    3510            }
    3511            return space;
    3512
    3513        }
    3514
    3515        /**
    3516         * Trims a rectangle to integer coordinates.
    3517         *
    3518         * @param rect  the incoming rectangle.
    3519         *
    3520         * @return A rectangle with integer coordinates.
    3521         */
    3522        private Rectangle integerise(Rectangle2D rect) {
    3523            int x0 = (int) Math.ceil(rect.getMinX());
    3524            int y0 = (int) Math.ceil(rect.getMinY());
    3525            int x1 = (int) Math.floor(rect.getMaxX());
    3526            int y1 = (int) Math.floor(rect.getMaxY());
    3527            return new Rectangle(x0, y0, (x1 - x0), (y1 - y0));
    3528        }
    3529
    3530        /**
    3531         * Calculates the space required for the axes.
    3532         *
    3533         * @param g2  the graphics device.
    3534         * @param plotArea  the plot area.
    3535         *
    3536         * @return The space required for the axes.
    3537         */
    3538        protected AxisSpace calculateAxisSpace(Graphics2D g2,
    3539                                               Rectangle2D plotArea) {
    3540            AxisSpace space = new AxisSpace();
    3541            space = calculateRangeAxisSpace(g2, plotArea, space);
    3542            space = calculateDomainAxisSpace(g2, plotArea, space);
    3543            return space;
    3544        }
    3545
    3546        /**
    3547         * Draws the plot on a Java 2D graphics device (such as the screen or a
    3548         * printer).
    3549         * <P>
    3550         * At your option, you may supply an instance of {@link PlotRenderingInfo}.
    3551         * If you do, it will be populated with information about the drawing,
    3552         * including various plot dimensions and tooltip info.
    3553         *
    3554         * @param g2  the graphics device.
    3555         * @param area  the area within which the plot (including axes) should
    3556         *              be drawn.
    3557         * @param anchor  the anchor point (<code>null</code> permitted).
    3558         * @param parentState  the state from the parent plot, if there is one.
    3559         * @param state  collects info as the chart is drawn (possibly
    3560         *               <code>null</code>).
    3561         */
    3562        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
    3563                PlotState parentState, PlotRenderingInfo state) {
    3564
    3565            // if the plot area is too small, just return...
    3566            boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
    3567            boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
    3568            if (b1 || b2) {
    3569                return;
    3570            }
    3571
    3572            // record the plot area...
    3573            if (state == null) {
    3574                // if the incoming state is null, no information will be passed
    3575                // back to the caller - but we create a temporary state to record
    3576                // the plot area, since that is used later by the axes
    3577                state = new PlotRenderingInfo(null);
    3578            }
    3579            state.setPlotArea(area);
    3580
    3581            // adjust the drawing area for the plot insets (if any)...
    3582            RectangleInsets insets = getInsets();
    3583            insets.trim(area);
    3584
    3585            // calculate the data area...
    3586            AxisSpace space = calculateAxisSpace(g2, area);
    3587            Rectangle2D dataArea = space.shrink(area, null);
    3588            this.axisOffset.trim(dataArea);
    3589            dataArea = integerise(dataArea);
    3590            if (dataArea.isEmpty()) {
    3591                return;
    3592            }
    3593            state.setDataArea(dataArea);
    3594            createAndAddEntity((Rectangle2D) dataArea.clone(), state, null, null);
    3595
    3596            // if there is a renderer, it draws the background, otherwise use the
    3597            // default background...
    3598            if (getRenderer() != null) {
    3599                getRenderer().drawBackground(g2, this, dataArea);
    3600            }
    3601            else {
    3602                drawBackground(g2, dataArea);
    3603            }
    3604
    3605            Map axisStateMap = drawAxes(g2, area, dataArea, state);
    3606
    3607            // the anchor point is typically the point where the mouse last
    3608            // clicked - the crosshairs will be driven off this point...
    3609            if (anchor != null && !dataArea.contains(anchor)) {
    3610                anchor = ShapeUtilities.getPointInRectangle(anchor.getX(),
    3611                        anchor.getY(), dataArea);
    3612            }
    3613            CategoryCrosshairState crosshairState = new CategoryCrosshairState();
    3614            crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
    3615            crosshairState.setAnchor(anchor);
    3616
    3617            // specify the anchor X and Y coordinates in Java2D space, for the
    3618            // cases where these are not updated during rendering (i.e. no lock
    3619            // on data)
    3620            crosshairState.setAnchorX(Double.NaN);
    3621            crosshairState.setAnchorY(Double.NaN);
    3622            if (anchor != null) {
    3623                ValueAxis rangeAxis = getRangeAxis();
    3624                if (rangeAxis != null) {
    3625                    double y;
    3626                    if (getOrientation() == PlotOrientation.VERTICAL) {
    3627                        y = rangeAxis.java2DToValue(anchor.getY(), dataArea,
    3628                                getRangeAxisEdge());
    3629                    }
    3630                    else {
    3631                        y = rangeAxis.java2DToValue(anchor.getX(), dataArea,
    3632                                getRangeAxisEdge());
    3633                    }
    3634                    crosshairState.setAnchorY(y);
    3635                }
    3636            }
    3637            crosshairState.setRowKey(getDomainCrosshairRowKey());
    3638            crosshairState.setColumnKey(getDomainCrosshairColumnKey());
    3639            crosshairState.setCrosshairY(getRangeCrosshairValue());
    3640
    3641            // don't let anyone draw outside the data area
    3642            Shape savedClip = g2.getClip();
    3643            g2.clip(dataArea);
    3644
    3645            drawDomainGridlines(g2, dataArea);
    3646
    3647            AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis());
    3648            if (rangeAxisState == null) {
    3649                if (parentState != null) {
    3650                    rangeAxisState = (AxisState) parentState.getSharedAxisStates()
    3651                            .get(getRangeAxis());
    3652                }
    3653            }
    3654            if (rangeAxisState != null) {
    3655                drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
    3656                drawZeroRangeBaseline(g2, dataArea);
    3657            }
    3658
    3659            Graphics2D savedG2 = g2;
    3660            BufferedImage dataImage = null;
    3661            if (this.shadowGenerator != null) {
    3662                dataImage = new BufferedImage((int) dataArea.getWidth(),
    3663                        (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB);
    3664                g2 = dataImage.createGraphics();
    3665                g2.translate(-dataArea.getX(), -dataArea.getY());
    3666                g2.setRenderingHints(savedG2.getRenderingHints());
    3667            }
    3668
    3669            // draw the markers...
    3670            for (int i = 0; i < this.renderers.size(); i++) {
    3671                drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND);
    3672            }
    3673            for (int i = 0; i < this.renderers.size(); i++) {
    3674                drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND);
    3675            }
    3676
    3677            // now render data items...
    3678            boolean foundData = false;
    3679
    3680            // set up the alpha-transparency...
    3681            Composite originalComposite = g2.getComposite();
    3682            g2.setComposite(AlphaComposite.getInstance(
    3683                    AlphaComposite.SRC_OVER, getForegroundAlpha()));
    3684
    3685            DatasetRenderingOrder order = getDatasetRenderingOrder();
    3686            if (order == DatasetRenderingOrder.FORWARD) {
    3687                for (int i = 0; i < this.datasets.size(); i++) {
    3688                    foundData = render(g2, dataArea, i, state, crosshairState)
    3689                        || foundData;
    3690                }
    3691            }
    3692            else {  // DatasetRenderingOrder.REVERSE
    3693                for (int i = this.datasets.size() - 1; i >= 0; i--) {
    3694                    foundData = render(g2, dataArea, i, state, crosshairState)
    3695                        || foundData;
    3696                }
    3697            }
    3698            // draw the foreground markers...
    3699            for (int i = 0; i < this.renderers.size(); i++) {
    3700                drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND);
    3701            }
    3702            for (int i = 0; i < this.renderers.size(); i++) {
    3703                drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND);
    3704            }
    3705
    3706            // draw the annotations (if any)...
    3707            drawAnnotations(g2, dataArea);
    3708
    3709            if (this.shadowGenerator != null) {
    3710                BufferedImage shadowImage = this.shadowGenerator.createDropShadow(
    3711                        dataImage);
    3712                g2 = savedG2;
    3713                g2.drawImage(shadowImage, (int) dataArea.getX()
    3714                        + this.shadowGenerator.calculateOffsetX(),
    3715                        (int) dataArea.getY()
    3716                        + this.shadowGenerator.calculateOffsetY(), null);
    3717                g2.drawImage(dataImage, (int) dataArea.getX(),
    3718                        (int) dataArea.getY(), null);
    3719            }
    3720            g2.setClip(savedClip);
    3721            g2.setComposite(originalComposite);
    3722
    3723            if (!foundData) {
    3724                drawNoDataMessage(g2, dataArea);
    3725            }
    3726
    3727            int datasetIndex = crosshairState.getDatasetIndex();
    3728            setCrosshairDatasetIndex(datasetIndex, false);
    3729
    3730            // draw domain crosshair if required...
    3731            Comparable rowKey = crosshairState.getRowKey();
    3732            Comparable columnKey = crosshairState.getColumnKey();
    3733            setDomainCrosshairRowKey(rowKey, false);
    3734            setDomainCrosshairColumnKey(columnKey, false);
    3735            if (isDomainCrosshairVisible() && columnKey != null) {
    3736                Paint paint = getDomainCrosshairPaint();
    3737                Stroke stroke = getDomainCrosshairStroke();
    3738                drawDomainCrosshair(g2, dataArea, this.orientation,
    3739                        datasetIndex, rowKey, columnKey, stroke, paint);
    3740            }
    3741
    3742            // draw range crosshair if required...
    3743            ValueAxis yAxis = getRangeAxisForDataset(datasetIndex);
    3744            RectangleEdge yAxisEdge = getRangeAxisEdge();
    3745            if (!this.rangeCrosshairLockedOnData && anchor != null) {
    3746                double yy;
    3747                if (getOrientation() == PlotOrientation.VERTICAL) {
    3748                    yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge);
    3749                }
    3750                else {
    3751                    yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge);
    3752                }
    3753                crosshairState.setCrosshairY(yy);
    3754            }
    3755            setRangeCrosshairValue(crosshairState.getCrosshairY(), false);
    3756            if (isRangeCrosshairVisible()) {
    3757                double y = getRangeCrosshairValue();
    3758                Paint paint = getRangeCrosshairPaint();
    3759                Stroke stroke = getRangeCrosshairStroke();
    3760                drawRangeCrosshair(g2, dataArea, getOrientation(), y, yAxis,
    3761                        stroke, paint);
    3762            }
    3763
    3764            // draw an outline around the plot area...
    3765            if (isOutlineVisible()) {
    3766                if (getRenderer() != null) {
    3767                    getRenderer().drawOutline(g2, this, dataArea);
    3768                }
    3769                else {
    3770                    drawOutline(g2, dataArea);
    3771                }
    3772            }
    3773
    3774        }
    3775
    3776        /**
    3777         * Draws the plot background (the background color and/or image).
    3778         * <P>
    3779         * This method will be called during the chart drawing process and is
    3780         * declared public so that it can be accessed by the renderers used by
    3781         * certain subclasses.  You shouldn't need to call this method directly.
    3782         *
    3783         * @param g2  the graphics device.
    3784         * @param area  the area within which the plot should be drawn.
    3785         */
    3786        public void drawBackground(Graphics2D g2, Rectangle2D area) {
    3787            fillBackground(g2, area, this.orientation);
    3788            drawBackgroundImage(g2, area);
    3789        }
    3790
    3791        /**
    3792         * A utility method for drawing the plot's axes.
    3793         *
    3794         * @param g2  the graphics device.
    3795         * @param plotArea  the plot area.
    3796         * @param dataArea  the data area.
    3797         * @param plotState  collects information about the plot (<code>null</code>
    3798         *                   permitted).
    3799         *
    3800         * @return A map containing the axis states.
    3801         */
    3802        protected Map drawAxes(Graphics2D g2,
    3803                               Rectangle2D plotArea,
    3804                               Rectangle2D dataArea,
    3805                               PlotRenderingInfo plotState) {
    3806
    3807            AxisCollection axisCollection = new AxisCollection();
    3808
    3809            // add domain axes to lists...
    3810            for (int index = 0; index < this.domainAxes.size(); index++) {
    3811                CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(index);
    3812                if (xAxis != null) {
    3813                    axisCollection.add(xAxis, getDomainAxisEdge(index));
    3814                }
    3815            }
    3816
    3817            // add range axes to lists...
    3818            for (int index = 0; index < this.rangeAxes.size(); index++) {
    3819                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
    3820                if (yAxis != null) {
    3821                    axisCollection.add(yAxis, getRangeAxisEdge(index));
    3822                }
    3823            }
    3824
    3825            Map axisStateMap = new HashMap();
    3826
    3827            // draw the top axes
    3828            double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
    3829                    dataArea.getHeight());
    3830            Iterator iterator = axisCollection.getAxesAtTop().iterator();
    3831            while (iterator.hasNext()) {
    3832                Axis axis = (Axis) iterator.next();
    3833                if (axis != null) {
    3834                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
    3835                            RectangleEdge.TOP, plotState);
    3836                    cursor = axisState.getCursor();
    3837                    axisStateMap.put(axis, axisState);
    3838                }
    3839            }
    3840
    3841            // draw the bottom axes
    3842            cursor = dataArea.getMaxY()
    3843                     + this.axisOffset.calculateBottomOutset(dataArea.getHeight());
    3844            iterator = axisCollection.getAxesAtBottom().iterator();
    3845            while (iterator.hasNext()) {
    3846                Axis axis = (Axis) iterator.next();
    3847                if (axis != null) {
    3848                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
    3849                            RectangleEdge.BOTTOM, plotState);
    3850                    cursor = axisState.getCursor();
    3851                    axisStateMap.put(axis, axisState);
    3852                }
    3853            }
    3854
    3855            // draw the left axes
    3856            cursor = dataArea.getMinX()
    3857                     - this.axisOffset.calculateLeftOutset(dataArea.getWidth());
    3858            iterator = axisCollection.getAxesAtLeft().iterator();
    3859            while (iterator.hasNext()) {
    3860                Axis axis = (Axis) iterator.next();
    3861                if (axis != null) {
    3862                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
    3863                            RectangleEdge.LEFT, plotState);
    3864                    cursor = axisState.getCursor();
    3865                    axisStateMap.put(axis, axisState);
    3866                }
    3867            }
    3868
    3869            // draw the right axes
    3870            cursor = dataArea.getMaxX()
    3871                     + this.axisOffset.calculateRightOutset(dataArea.getWidth());
    3872            iterator = axisCollection.getAxesAtRight().iterator();
    3873            while (iterator.hasNext()) {
    3874                Axis axis = (Axis) iterator.next();
    3875                if (axis != null) {
    3876                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
    3877                            RectangleEdge.RIGHT, plotState);
    3878                    cursor = axisState.getCursor();
    3879                    axisStateMap.put(axis, axisState);
    3880                }
    3881            }
    3882
    3883            return axisStateMap;
    3884
    3885        }
    3886
    3887        /**
    3888         * Draws a representation of a dataset within the dataArea region using the
    3889         * appropriate renderer.
    3890         *
    3891         * @param g2  the graphics device.
    3892         * @param dataArea  the region in which the data is to be drawn.
    3893         * @param index  the dataset and renderer index.
    3894         * @param info  an optional object for collection dimension information.
    3895         * @param crosshairState  a state object for tracking crosshair info
    3896         *        (<code>null</code> permitted).
    3897         *
    3898         * @return A boolean that indicates whether or not real data was found.
    3899         *
    3900         * @since 1.0.11
    3901         */
    3902        public boolean render(Graphics2D g2, Rectangle2D dataArea, int index,
    3903                PlotRenderingInfo info, CategoryCrosshairState crosshairState) {
    3904
    3905            boolean foundData = false;
    3906            CategoryDataset currentDataset = getDataset(index);
    3907            CategoryItemRenderer renderer = getRenderer(index);
    3908            CategoryAxis domainAxis = getDomainAxisForDataset(index);
    3909            ValueAxis rangeAxis = getRangeAxisForDataset(index);
    3910            boolean hasData = !DatasetUtilities.isEmptyOrNull(currentDataset);
    3911            if (hasData && renderer != null) {
    3912
    3913                foundData = true;
    3914                CategoryItemRendererState state = renderer.initialise(g2, dataArea,
    3915                        this, index, info);
    3916                state.setCrosshairState(crosshairState);
    3917                int columnCount = currentDataset.getColumnCount();
    3918                int rowCount = currentDataset.getRowCount();
    3919                int passCount = renderer.getPassCount();
    3920                for (int pass = 0; pass < passCount; pass++) {
    3921                    if (this.columnRenderingOrder == SortOrder.ASCENDING) {
    3922                        for (int column = 0; column < columnCount; column++) {
    3923                            if (this.rowRenderingOrder == SortOrder.ASCENDING) {
    3924                                for (int row = 0; row < rowCount; row++) {
    3925                                    renderer.drawItem(g2, state, dataArea, this,
    3926                                            domainAxis, rangeAxis, currentDataset,
    3927                                            row, column, pass);
    3928                                }
    3929                            }
    3930                            else {
    3931                                for (int row = rowCount - 1; row >= 0; row--) {
    3932                                    renderer.drawItem(g2, state, dataArea, this,
    3933                                            domainAxis, rangeAxis, currentDataset,
    3934                                            row, column, pass);
    3935                                }
    3936                            }
    3937                        }
    3938                    }
    3939                    else {
    3940                        for (int column = columnCount - 1; column >= 0; column--) {
    3941                            if (this.rowRenderingOrder == SortOrder.ASCENDING) {
    3942                                for (int row = 0; row < rowCount; row++) {
    3943                                    renderer.drawItem(g2, state, dataArea, this,
    3944                                            domainAxis, rangeAxis, currentDataset,
    3945                                            row, column, pass);
    3946                                }
    3947                            }
    3948                            else {
    3949                                for (int row = rowCount - 1; row >= 0; row--) {
    3950                                    renderer.drawItem(g2, state, dataArea, this,
    3951                                            domainAxis, rangeAxis, currentDataset,
    3952                                            row, column, pass);
    3953                                }
    3954                            }
    3955                        }
    3956                    }
    3957                }
    3958            }
    3959            return foundData;
    3960
    3961        }
    3962
    3963        /**
    3964         * Draws the domain gridlines for the plot, if they are visible.
    3965         *
    3966         * @param g2  the graphics device.
    3967         * @param dataArea  the area inside the axes.
    3968         *
    3969         * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List)
    3970         */
    3971        protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea) {
    3972
    3973            if (!isDomainGridlinesVisible()) {
    3974                return;
    3975            }
    3976            CategoryAnchor anchor = getDomainGridlinePosition();
    3977            RectangleEdge domainAxisEdge = getDomainAxisEdge();
    3978            CategoryDataset dataset = getDataset();
    3979            if (dataset == null) {
    3980                return;
    3981            }
    3982            CategoryAxis axis = getDomainAxis();
    3983            if (axis != null) {
    3984                int columnCount = dataset.getColumnCount();
    3985                for (int c = 0; c < columnCount; c++) {
    3986                    double xx = axis.getCategoryJava2DCoordinate(anchor, c,
    3987                            columnCount, dataArea, domainAxisEdge);
    3988                    CategoryItemRenderer renderer1 = getRenderer();
    3989                    if (renderer1 != null) {
    3990                        renderer1.drawDomainGridline(g2, this, dataArea, xx);
    3991                    }
    3992                }
    3993            }
    3994        }
    3995
    3996        /**
    3997         * Draws the range gridlines for the plot, if they are visible.
    3998         *
    3999         * @param g2  the graphics device.
    4000         * @param dataArea  the area inside the axes.
    4001         * @param ticks  the ticks.
    4002         *
    4003         * @see #drawDomainGridlines(Graphics2D, Rectangle2D)
    4004         */
    4005        protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea,
    4006                                          List ticks) {
    4007            // draw the range grid lines, if any...
    4008            if (!isRangeGridlinesVisible() && !isRangeMinorGridlinesVisible()) {
    4009                return;
    4010            }
    4011            // no axis, no gridlines...
    4012            ValueAxis axis = getRangeAxis();
    4013            if (axis == null) {
    4014                return;
    4015            }
    4016            // no renderer, no gridlines...
    4017            CategoryItemRenderer r = getRenderer();
    4018            if (r == null) {
    4019                return;
    4020            }
    4021
    4022            Stroke gridStroke = null;
    4023            Paint gridPaint = null;
    4024            boolean paintLine = false;
    4025            Iterator iterator = ticks.iterator();
    4026            while (iterator.hasNext()) {
    4027                paintLine = false;
    4028                ValueTick tick = (ValueTick) iterator.next();
    4029                if ((tick.getTickType() == TickType.MINOR)
    4030                        && isRangeMinorGridlinesVisible()) {
    4031                    gridStroke = getRangeMinorGridlineStroke();
    4032                    gridPaint = getRangeMinorGridlinePaint();
    4033                    paintLine = true;
    4034                }
    4035                else if ((tick.getTickType() == TickType.MAJOR)
    4036                        && isRangeGridlinesVisible()) {
    4037                    gridStroke = getRangeGridlineStroke();
    4038                    gridPaint = getRangeGridlinePaint();
    4039                    paintLine = true;
    4040                }
    4041                if (((tick.getValue() != 0.0)
    4042                        || !isRangeZeroBaselineVisible()) && paintLine) {
    4043                    // the method we want isn't in the CategoryItemRenderer
    4044                    // interface...
    4045                    if (r instanceof AbstractCategoryItemRenderer) {
    4046                        AbstractCategoryItemRenderer aci
    4047                                = (AbstractCategoryItemRenderer) r;
    4048                        aci.drawRangeLine(g2, this, axis, dataArea,
    4049                                tick.getValue(), gridPaint, gridStroke);
    4050                    }
    4051                    else {
    4052                        // we'll have to use the method in the interface, but
    4053                        // this doesn't have the paint and stroke settings...
    4054                        r.drawRangeGridline(g2, this, axis, dataArea,
    4055                                tick.getValue());
    4056                    }
    4057                }
    4058            }
    4059        }
    4060
    4061        /**
    4062         * Draws a base line across the chart at value zero on the range axis.
    4063         *
    4064         * @param g2  the graphics device.
    4065         * @param area  the data area.
    4066         *
    4067         * @see #setRangeZeroBaselineVisible(boolean)
    4068         *
    4069         * @since 1.0.13
    4070         */
    4071        protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) {
    4072            if (!isRangeZeroBaselineVisible()) {
    4073                return;
    4074            }
    4075            CategoryItemRenderer r = getRenderer();
    4076            if (r instanceof AbstractCategoryItemRenderer) {
    4077                AbstractCategoryItemRenderer aci = (AbstractCategoryItemRenderer) r;
    4078                aci.drawRangeLine(g2, this, getRangeAxis(), area, 0.0,
    4079                        this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke);
    4080            }
    4081            else {
    4082                r.drawRangeGridline(g2, this, getRangeAxis(), area, 0.0);
    4083            }
    4084        }
    4085
    4086        /**
    4087         * Draws the annotations.
    4088         *
    4089         * @param g2  the graphics device.
    4090         * @param dataArea  the data area.
    4091         */
    4092        protected void drawAnnotations(Graphics2D g2, Rectangle2D dataArea) {
    4093
    4094            if (getAnnotations() != null) {
    4095                Iterator iterator = getAnnotations().iterator();
    4096                while (iterator.hasNext()) {
    4097                    CategoryAnnotation annotation
    4098                            = (CategoryAnnotation) iterator.next();
    4099                    annotation.draw(g2, this, dataArea, getDomainAxis(),
    4100                            getRangeAxis());
    4101                }
    4102            }
    4103
    4104        }
    4105
    4106        /**
    4107         * Draws the domain markers (if any) for an axis and layer.  This method is
    4108         * typically called from within the draw() method.
    4109         *
    4110         * @param g2  the graphics device.
    4111         * @param dataArea  the data area.
    4112         * @param index  the renderer index.
    4113         * @param layer  the layer (foreground or background).
    4114         *
    4115         * @see #drawRangeMarkers(Graphics2D, Rectangle2D, int, Layer)
    4116         */
    4117        protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea,
    4118                                         int index, Layer layer) {
    4119
    4120            CategoryItemRenderer r = getRenderer(index);
    4121            if (r == null) {
    4122                return;
    4123            }
    4124
    4125            Collection markers = getDomainMarkers(index, layer);
    4126            CategoryAxis axis = getDomainAxisForDataset(index);
    4127            if (markers != null && axis != null) {
    4128                Iterator iterator = markers.iterator();
    4129                while (iterator.hasNext()) {
    4130                    CategoryMarker marker = (CategoryMarker) iterator.next();
    4131                    r.drawDomainMarker(g2, this, axis, marker, dataArea);
    4132                }
    4133            }
    4134
    4135        }
    4136
    4137        /**
    4138         * Draws the range markers (if any) for an axis and layer.  This method is
    4139         * typically called from within the draw() method.
    4140         *
    4141         * @param g2  the graphics device.
    4142         * @param dataArea  the data area.
    4143         * @param index  the renderer index.
    4144         * @param layer  the layer (foreground or background).
    4145         *
    4146         * @see #drawDomainMarkers(Graphics2D, Rectangle2D, int, Layer)
    4147         */
    4148        protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea,
    4149                                        int index, Layer layer) {
    4150
    4151            CategoryItemRenderer r = getRenderer(index);
    4152            if (r == null) {
    4153                return;
    4154            }
    4155
    4156            Collection markers = getRangeMarkers(index, layer);
    4157            ValueAxis axis = getRangeAxisForDataset(index);
    4158            if (markers != null && axis != null) {
    4159                Iterator iterator = markers.iterator();
    4160                while (iterator.hasNext()) {
    4161                    Marker marker = (Marker) iterator.next();
    4162                    r.drawRangeMarker(g2, this, axis, marker, dataArea);
    4163                }
    4164            }
    4165
    4166        }
    4167
    4168        /**
    4169         * Utility method for drawing a line perpendicular to the range axis (used
    4170         * for crosshairs).
    4171         *
    4172         * @param g2  the graphics device.
    4173         * @param dataArea  the area defined by the axes.
    4174         * @param value  the data value.
    4175         * @param stroke  the line stroke (<code>null</code> not permitted).
    4176         * @param paint  the line paint (<code>null</code> not permitted).
    4177         */
    4178        protected void drawRangeLine(Graphics2D g2, Rectangle2D dataArea,
    4179                double value, Stroke stroke, Paint paint) {
    4180
    4181            double java2D = getRangeAxis().valueToJava2D(value, dataArea,
    4182                    getRangeAxisEdge());
    4183            Line2D line = null;
    4184            if (this.orientation == PlotOrientation.HORIZONTAL) {
    4185                line = new Line2D.Double(java2D, dataArea.getMinY(), java2D,
    4186                        dataArea.getMaxY());
    4187            }
    4188            else if (this.orientation == PlotOrientation.VERTICAL) {
    4189                line = new Line2D.Double(dataArea.getMinX(), java2D,
    4190                        dataArea.getMaxX(), java2D);
    4191            }
    4192            g2.setStroke(stroke);
    4193            g2.setPaint(paint);
    4194            g2.draw(line);
    4195
    4196        }
    4197
    4198        /**
    4199         * Draws a domain crosshair.
    4200         *
    4201         * @param g2  the graphics target.
    4202         * @param dataArea  the data area.
    4203         * @param orientation  the plot orientation.
    4204         * @param datasetIndex  the dataset index.
    4205         * @param rowKey  the row key.
    4206         * @param columnKey  the column key.
    4207         * @param stroke  the stroke used to draw the crosshair line.
    4208         * @param paint  the paint used to draw the crosshair line.
    4209         *
    4210         * @see #drawRangeCrosshair(Graphics2D, Rectangle2D, PlotOrientation,
    4211         *     double, ValueAxis, Stroke, Paint)
    4212         *
    4213         * @since 1.0.11
    4214         */
    4215        protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea,
    4216                PlotOrientation orientation, int datasetIndex,
    4217                Comparable rowKey, Comparable columnKey, Stroke stroke,
    4218                Paint paint) {
    4219
    4220            CategoryDataset dataset = getDataset(datasetIndex);
    4221            CategoryAxis axis = getDomainAxisForDataset(datasetIndex);
    4222            CategoryItemRenderer renderer = getRenderer(datasetIndex);
    4223            Line2D line = null;
    4224            if (orientation == PlotOrientation.VERTICAL) {
    4225                double xx = renderer.getItemMiddle(rowKey, columnKey, dataset, axis,
    4226                        dataArea, RectangleEdge.BOTTOM);
    4227                line = new Line2D.Double(xx, dataArea.getMinY(), xx,
    4228                        dataArea.getMaxY());
    4229            }
    4230            else {
    4231                double yy = renderer.getItemMiddle(rowKey, columnKey, dataset, axis,
    4232                        dataArea, RectangleEdge.LEFT);
    4233                line = new Line2D.Double(dataArea.getMinX(), yy,
    4234                        dataArea.getMaxX(), yy);
    4235            }
    4236            g2.setStroke(stroke);
    4237            g2.setPaint(paint);
    4238            g2.draw(line);
    4239
    4240        }
    4241
    4242        /**
    4243         * Draws a range crosshair.
    4244         *
    4245         * @param g2  the graphics target.
    4246         * @param dataArea  the data area.
    4247         * @param orientation  the plot orientation.
    4248         * @param value  the crosshair value.
    4249         * @param axis  the axis against which the value is measured.
    4250         * @param stroke  the stroke used to draw the crosshair line.
    4251         * @param paint  the paint used to draw the crosshair line.
    4252         *
    4253         * @see #drawDomainCrosshair(Graphics2D, Rectangle2D, PlotOrientation, int,
    4254         *      Comparable, Comparable, Stroke, Paint)
    4255         *
    4256         * @since 1.0.5
    4257         */
    4258        protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea,
    4259                PlotOrientation orientation, double value, ValueAxis axis,
    4260                Stroke stroke, Paint paint) {
    4261
    4262            if (!axis.getRange().contains(value)) {
    4263                return;
    4264            }
    4265            Line2D line = null;
    4266            if (orientation == PlotOrientation.HORIZONTAL) {
    4267                double xx = axis.valueToJava2D(value, dataArea,
    4268                        RectangleEdge.BOTTOM);
    4269                line = new Line2D.Double(xx, dataArea.getMinY(), xx,
    4270                        dataArea.getMaxY());
    4271            }
    4272            else {
    4273                double yy = axis.valueToJava2D(value, dataArea,
    4274                        RectangleEdge.LEFT);
    4275                line = new Line2D.Double(dataArea.getMinX(), yy,
    4276                        dataArea.getMaxX(), yy);
    4277            }
    4278            g2.setStroke(stroke);
    4279            g2.setPaint(paint);
    4280            g2.draw(line);
    4281
    4282        }
    4283
    4284        /**
    4285         * Returns the range of data values that will be plotted against the range
    4286         * axis.  If the dataset is <code>null</code>, this method returns
    4287         * <code>null</code>.
    4288         *
    4289         * @param axis  the axis.
    4290         *
    4291         * @return The data range.
    4292         */
    4293        public Range getDataRange(ValueAxis axis) {
    4294
    4295            Range result = null;
    4296            List mappedDatasets = new ArrayList();
    4297
    4298            int rangeIndex = this.rangeAxes.indexOf(axis);
    4299            if (rangeIndex >= 0) {
    4300                mappedDatasets.addAll(datasetsMappedToRangeAxis(rangeIndex));
    4301            }
    4302            else if (axis == getRangeAxis()) {
    4303                mappedDatasets.addAll(datasetsMappedToRangeAxis(0));
    4304            }
    4305
    4306            // iterate through the datasets that map to the axis and get the union
    4307            // of the ranges.
    4308            Iterator iterator = mappedDatasets.iterator();
    4309            while (iterator.hasNext()) {
    4310                CategoryDataset d = (CategoryDataset) iterator.next();
    4311                CategoryItemRenderer r = getRendererForDataset(d);
    4312                if (r != null) {
    4313                    result = Range.combine(result, r.findRangeBounds(d));
    4314                }
    4315            }
    4316            return result;
    4317
    4318        }
    4319
    4320        /**
    4321         * Returns a list of the datasets that are mapped to the axis with the
    4322         * specified index.
    4323         *
    4324         * @param axisIndex  the axis index.
    4325         *
    4326         * @return The list (possibly empty, but never <code>null</code>).
    4327         *
    4328         * @since 1.0.3
    4329         */
    4330        private List datasetsMappedToDomainAxis(int axisIndex) {
    4331            Integer key = new Integer(axisIndex);
    4332            List result = new ArrayList();
    4333            for (int i = 0; i < this.datasets.size(); i++) {
    4334                List mappedAxes = (List) this.datasetToDomainAxesMap.get(
    4335                        new Integer(i));
    4336                CategoryDataset dataset = (CategoryDataset) this.datasets.get(i);
    4337                if (mappedAxes == null) {
    4338                    if (key.equals(ZERO)) {
    4339                        if (dataset != null) {
    4340                            result.add(dataset);
    4341                        }
    4342                    }
    4343                }
    4344                else {
    4345                    if (mappedAxes.contains(key)) {
    4346                        if (dataset != null) {
    4347                            result.add(dataset);
    4348                        }
    4349                    }
    4350                }
    4351            }
    4352            return result;
    4353        }
    4354
    4355        /**
    4356         * A utility method that returns a list of datasets that are mapped to a
    4357         * given range axis.
    4358         *
    4359         * @param index  the axis index.
    4360         *
    4361         * @return A list of datasets.
    4362         */
    4363        private List datasetsMappedToRangeAxis(int index) {
    4364            Integer key = new Integer(index);
    4365            List result = new ArrayList();
    4366            for (int i = 0; i < this.datasets.size(); i++) {
    4367                List mappedAxes = (List) this.datasetToRangeAxesMap.get(
    4368                        new Integer(i));
    4369                if (mappedAxes == null) {
    4370                    if (key.equals(ZERO)) {
    4371                        result.add(this.datasets.get(i));
    4372                    }
    4373                }
    4374                else {
    4375                    if (mappedAxes.contains(key)) {
    4376                        result.add(this.datasets.get(i));
    4377                    }
    4378                }
    4379            }
    4380            return result;
    4381        }
    4382
    4383        /**
    4384         * Returns the weight for this plot when it is used as a subplot within a
    4385         * combined plot.
    4386         *
    4387         * @return The weight.
    4388         *
    4389         * @see #setWeight(int)
    4390         */
    4391        public int getWeight() {
    4392            return this.weight;
    4393        }
    4394
    4395        /**
    4396         * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all
    4397         * registered listeners.
    4398         *
    4399         * @param weight  the weight.
    4400         *
    4401         * @see #getWeight()
    4402         */
    4403        public void setWeight(int weight) {
    4404            this.weight = weight;
    4405            fireChangeEvent();
    4406        }
    4407
    4408        /**
    4409         * Returns the fixed domain axis space.
    4410         *
    4411         * @return The fixed domain axis space (possibly <code>null</code>).
    4412         *
    4413         * @see #setFixedDomainAxisSpace(AxisSpace)
    4414         */
    4415        public AxisSpace getFixedDomainAxisSpace() {
    4416            return this.fixedDomainAxisSpace;
    4417        }
    4418
    4419        /**
    4420         * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
    4421         * all registered listeners.
    4422         *
    4423         * @param space  the space (<code>null</code> permitted).
    4424         *
    4425         * @see #getFixedDomainAxisSpace()
    4426         */
    4427        public void setFixedDomainAxisSpace(AxisSpace space) {
    4428            setFixedDomainAxisSpace(space, true);
    4429        }
    4430
    4431        /**
    4432         * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
    4433         * all registered listeners.
    4434         *
    4435         * @param space  the space (<code>null</code> permitted).
    4436         * @param notify  notify listeners?
    4437         *
    4438         * @see #getFixedDomainAxisSpace()
    4439         *
    4440         * @since 1.0.7
    4441         */
    4442        public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) {
    4443            this.fixedDomainAxisSpace = space;
    4444            if (notify) {
    4445                fireChangeEvent();
    4446            }
    4447        }
    4448
    4449        /**
    4450         * Returns the fixed range axis space.
    4451         *
    4452         * @return The fixed range axis space (possibly <code>null</code>).
    4453         *
    4454         * @see #setFixedRangeAxisSpace(AxisSpace)
    4455         */
    4456        public AxisSpace getFixedRangeAxisSpace() {
    4457            return this.fixedRangeAxisSpace;
    4458        }
    4459
    4460        /**
    4461         * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
    4462         * all registered listeners.
    4463         *
    4464         * @param space  the space (<code>null</code> permitted).
    4465         *
    4466         * @see #getFixedRangeAxisSpace()
    4467         */
    4468        public void setFixedRangeAxisSpace(AxisSpace space) {
    4469            setFixedRangeAxisSpace(space, true);
    4470        }
    4471
    4472        /**
    4473         * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
    4474         * all registered listeners.
    4475         *
    4476         * @param space  the space (<code>null</code> permitted).
    4477         * @param notify  notify listeners?
    4478         *
    4479         * @see #getFixedRangeAxisSpace()
    4480         *
    4481         * @since 1.0.7
    4482         */
    4483        public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) {
    4484            this.fixedRangeAxisSpace = space;
    4485            if (notify) {
    4486                fireChangeEvent();
    4487            }
    4488        }
    4489
    4490        /**
    4491         * Returns a list of the categories in the plot's primary dataset.
    4492         *
    4493         * @return A list of the categories in the plot's primary dataset.
    4494         *
    4495         * @see #getCategoriesForAxis(CategoryAxis)
    4496         */
    4497        public List getCategories() {
    4498            List result = null;
    4499            if (getDataset() != null) {
    4500                result = Collections.unmodifiableList(getDataset().getColumnKeys());
    4501            }
    4502            return result;
    4503        }
    4504
    4505        /**
    4506         * Returns a list of the categories that should be displayed for the
    4507         * specified axis.
    4508         *
    4509         * @param axis  the axis (<code>null</code> not permitted)
    4510         *
    4511         * @return The categories.
    4512         *
    4513         * @since 1.0.3
    4514         */
    4515        public List getCategoriesForAxis(CategoryAxis axis) {
    4516            List result = new ArrayList();
    4517            int axisIndex = this.domainAxes.indexOf(axis);
    4518            List datasets = datasetsMappedToDomainAxis(axisIndex);
    4519            Iterator iterator = datasets.iterator();
    4520            while (iterator.hasNext()) {
    4521                CategoryDataset dataset = (CategoryDataset) iterator.next();
    4522                // add the unique categories from this dataset
    4523                for (int i = 0; i < dataset.getColumnCount(); i++) {
    4524                    Comparable category = dataset.getColumnKey(i);
    4525                    if (!result.contains(category)) {
    4526                        result.add(category);
    4527                    }
    4528                }
    4529            }
    4530            return result;
    4531        }
    4532
    4533        /**
    4534         * Returns the flag that controls whether or not the shared domain axis is
    4535         * drawn for each subplot.
    4536         *
    4537         * @return A boolean.
    4538         *
    4539         * @see #setDrawSharedDomainAxis(boolean)
    4540         */
    4541        public boolean getDrawSharedDomainAxis() {
    4542            return this.drawSharedDomainAxis;
    4543        }
    4544
    4545        /**
    4546         * Sets the flag that controls whether the shared domain axis is drawn when
    4547         * this plot is being used as a subplot.
    4548         *
    4549         * @param draw  a boolean.
    4550         *
    4551         * @see #getDrawSharedDomainAxis()
    4552         */
    4553        public void setDrawSharedDomainAxis(boolean draw) {
    4554            this.drawSharedDomainAxis = draw;
    4555            fireChangeEvent();
    4556        }
    4557
    4558        /**
    4559         * Returns <code>false</code> always, because the plot cannot be panned
    4560         * along the domain axis/axes.
    4561         *
    4562         * @return A boolean.
    4563         *
    4564         * @see #isRangePannable()
    4565         *
    4566         * @since 1.0.13
    4567         */
    4568        public boolean isDomainPannable() {
    4569            return false;
    4570        }
    4571
    4572        /**
    4573         * Returns <code>true</code> if panning is enabled for the range axes,
    4574         * and <code>false</code> otherwise.
    4575         *
    4576         * @return A boolean.
    4577         *
    4578         * @see #setRangePannable(boolean)
    4579         * @see #isDomainPannable()
    4580         *
    4581         * @since 1.0.13
    4582         */
    4583        public boolean isRangePannable() {
    4584            return this.rangePannable;
    4585        }
    4586
    4587        /**
    4588         * Sets the flag that enables or disables panning of the plot along
    4589         * the range axes.
    4590         *
    4591         * @param pannable  the new flag value.
    4592         *
    4593         * @see #isRangePannable()
    4594         *
    4595         * @since 1.0.13
    4596         */
    4597        public void setRangePannable(boolean pannable) {
    4598            this.rangePannable = pannable;
    4599        }
    4600
    4601        /**
    4602         * Pans the domain axes by the specified percentage.
    4603         *
    4604         * @param percent  the distance to pan (as a percentage of the axis length).
    4605         * @param info the plot info
    4606         * @param source the source point where the pan action started.
    4607         *
    4608         * @since 1.0.13
    4609         */
    4610        public void panDomainAxes(double percent, PlotRenderingInfo info,
    4611                Point2D source) {
    4612            // do nothing, because the plot is not pannable along the domain axes
    4613        }
    4614
    4615        /**
    4616         * Pans the range axes by the specified percentage.
    4617         *
    4618         * @param percent  the distance to pan (as a percentage of the axis length).
    4619         * @param info the plot info
    4620         * @param source the source point where the pan action started.
    4621         *
    4622         * @since 1.0.13
    4623         */
    4624        public void panRangeAxes(double percent, PlotRenderingInfo info,
    4625                Point2D source) {
    4626            if (!isRangePannable()) {
    4627                return;
    4628            }
    4629            int rangeAxisCount = getRangeAxisCount();
    4630            for (int i = 0; i < rangeAxisCount; i++) {
    4631                ValueAxis axis = getRangeAxis(i);
    4632                if (axis == null) {
    4633                    continue;
    4634                }
    4635                double length = axis.getRange().getLength();
    4636                double adj = percent * length;
    4637                if (axis.isInverted()) {
    4638                    adj = -adj;
    4639                }
    4640                axis.setRange(axis.getLowerBound() + adj,
    4641                        axis.getUpperBound() + adj);
    4642            }
    4643        }
    4644
    4645        /**
    4646         * Returns <code>false</code> to indicate that the domain axes are not
    4647         * zoomable.
    4648         *
    4649         * @return A boolean.
    4650         *
    4651         * @see #isRangeZoomable()
    4652         */
    4653        public boolean isDomainZoomable() {
    4654            return false;
    4655        }
    4656
    4657        /**
    4658         * Returns <code>true</code> to indicate that the range axes are zoomable.
    4659         *
    4660         * @return A boolean.
    4661         *
    4662         * @see #isDomainZoomable()
    4663         */
    4664        public boolean isRangeZoomable() {
    4665            return true;
    4666        }
    4667
    4668        /**
    4669         * This method does nothing, because <code>CategoryPlot</code> doesn't
    4670         * support zooming on the domain.
    4671         *
    4672         * @param factor  the zoom factor.
    4673         * @param state  the plot state.
    4674         * @param source  the source point (in Java2D space) for the zoom.
    4675         */
    4676        public void zoomDomainAxes(double factor, PlotRenderingInfo state,
    4677                                   Point2D source) {
    4678            // can't zoom domain axis
    4679        }
    4680
    4681        /**
    4682         * This method does nothing, because <code>CategoryPlot</code> doesn't
    4683         * support zooming on the domain.
    4684         *
    4685         * @param lowerPercent  the lower bound.
    4686         * @param upperPercent  the upper bound.
    4687         * @param state  the plot state.
    4688         * @param source  the source point (in Java2D space) for the zoom.
    4689         */
    4690        public void zoomDomainAxes(double lowerPercent, double upperPercent,
    4691                                   PlotRenderingInfo state, Point2D source) {
    4692            // can't zoom domain axis
    4693        }
    4694
    4695        /**
    4696         * This method does nothing, because <code>CategoryPlot</code> doesn't
    4697         * support zooming on the domain.
    4698         *
    4699         * @param factor  the zoom factor.
    4700         * @param info  the plot rendering info.
    4701         * @param source  the source point (in Java2D space).
    4702         * @param useAnchor  use source point as zoom anchor?
    4703         *
    4704         * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean)
    4705         *
    4706         * @since 1.0.7
    4707         */
    4708        public void zoomDomainAxes(double factor, PlotRenderingInfo info,
    4709                                   Point2D source, boolean useAnchor) {
    4710            // can't zoom domain axis
    4711        }
    4712
    4713        /**
    4714         * Multiplies the range on the range axis/axes by the specified factor.
    4715         *
    4716         * @param factor  the zoom factor.
    4717         * @param state  the plot state.
    4718         * @param source  the source point (in Java2D space) for the zoom.
    4719         */
    4720        public void zoomRangeAxes(double factor, PlotRenderingInfo state,
    4721                                  Point2D source) {
    4722            // delegate to other method
    4723            zoomRangeAxes(factor, state, source, false);
    4724        }
    4725
    4726        /**
    4727         * Multiplies the range on the range axis/axes by the specified factor.
    4728         *
    4729         * @param factor  the zoom factor.
    4730         * @param info  the plot rendering info.
    4731         * @param source  the source point.
    4732         * @param useAnchor  a flag that controls whether or not the source point
    4733         *         is used for the zoom anchor.
    4734         *
    4735         * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
    4736         *
    4737         * @since 1.0.7
    4738         */
    4739        public void zoomRangeAxes(double factor, PlotRenderingInfo info,
    4740                                  Point2D source, boolean useAnchor) {
    4741
    4742            // perform the zoom on each range axis
    4743            for (int i = 0; i < this.rangeAxes.size(); i++) {
    4744                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
    4745                if (rangeAxis != null) {
    4746                    if (useAnchor) {
    4747                        // get the relevant source coordinate given the plot
    4748                        // orientation
    4749                        double sourceY = source.getY();
    4750                        if (this.orientation == PlotOrientation.HORIZONTAL) {
    4751                            sourceY = source.getX();
    4752                        }
    4753                        double anchorY = rangeAxis.java2DToValue(sourceY,
    4754                                info.getDataArea(), getRangeAxisEdge());
    4755                        rangeAxis.resizeRange2(factor, anchorY);
    4756                    }
    4757                    else {
    4758                        rangeAxis.resizeRange(factor);
    4759                    }
    4760                }
    4761            }
    4762        }
    4763
    4764        /**
    4765         * Zooms in on the range axes.
    4766         *
    4767         * @param lowerPercent  the lower bound.
    4768         * @param upperPercent  the upper bound.
    4769         * @param state  the plot state.
    4770         * @param source  the source point (in Java2D space) for the zoom.
    4771         */
    4772        public void zoomRangeAxes(double lowerPercent, double upperPercent,
    4773                                  PlotRenderingInfo state, Point2D source) {
    4774            for (int i = 0; i < this.rangeAxes.size(); i++) {
    4775                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
    4776                if (rangeAxis != null) {
    4777                    rangeAxis.zoomRange(lowerPercent, upperPercent);
    4778                }
    4779            }
    4780        }
    4781
    4782        /**
    4783         * Returns the anchor value.
    4784         *
    4785         * @return The anchor value.
    4786         *
    4787         * @see #setAnchorValue(double)
    4788         */
    4789        public double getAnchorValue() {
    4790            return this.anchorValue;
    4791        }
    4792
    4793        /**
    4794         * Sets the anchor value and sends a {@link PlotChangeEvent} to all
    4795         * registered listeners.
    4796         *
    4797         * @param value  the anchor value.
    4798         *
    4799         * @see #getAnchorValue()
    4800         */
    4801        public void setAnchorValue(double value) {
    4802            setAnchorValue(value, true);
    4803        }
    4804
    4805        /**
    4806         * Sets the anchor value and, if requested, sends a {@link PlotChangeEvent}
    4807         * to all registered listeners.
    4808         *
    4809         * @param value  the value.
    4810         * @param notify  notify listeners?
    4811         *
    4812         * @see #getAnchorValue()
    4813         */
    4814        public void setAnchorValue(double value, boolean notify) {
    4815            this.anchorValue = value;
    4816            if (notify) {
    4817                fireChangeEvent();
    4818            }
    4819        }
    4820
    4821        /**
    4822         * Tests the plot for equality with an arbitrary object.
    4823         *
    4824         * @param obj  the object to test against (<code>null</code> permitted).
    4825         *
    4826         * @return A boolean.
    4827         */
    4828        public boolean equals(Object obj) {
    4829            if (obj == this) {
    4830                return true;
    4831            }
    4832            if (!(obj instanceof CategoryPlot)) {
    4833                return false;
    4834            }
    4835            CategoryPlot that = (CategoryPlot) obj;
    4836            if (this.orientation != that.orientation) {
    4837                return false;
    4838            }
    4839            if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) {
    4840                return false;
    4841            }
    4842            if (!this.domainAxes.equals(that.domainAxes)) {
    4843                return false;
    4844            }
    4845            if (!this.domainAxisLocations.equals(that.domainAxisLocations)) {
    4846                return false;
    4847            }
    4848            if (this.drawSharedDomainAxis != that.drawSharedDomainAxis) {
    4849                return false;
    4850            }
    4851            if (!this.rangeAxes.equals(that.rangeAxes)) {
    4852                return false;
    4853            }
    4854            if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) {
    4855                return false;
    4856            }
    4857            if (!ObjectUtilities.equal(this.datasetToDomainAxesMap,
    4858                    that.datasetToDomainAxesMap)) {
    4859                return false;
    4860            }
    4861            if (!ObjectUtilities.equal(this.datasetToRangeAxesMap,
    4862                    that.datasetToRangeAxesMap)) {
    4863                return false;
    4864            }
    4865            if (!ObjectUtilities.equal(this.renderers, that.renderers)) {
    4866                return false;
    4867            }
    4868            if (this.renderingOrder != that.renderingOrder) {
    4869                return false;
    4870            }
    4871            if (this.columnRenderingOrder != that.columnRenderingOrder) {
    4872                return false;
    4873            }
    4874            if (this.rowRenderingOrder != that.rowRenderingOrder) {
    4875                return false;
    4876            }
    4877            if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
    4878                return false;
    4879            }
    4880            if (this.domainGridlinePosition != that.domainGridlinePosition) {
    4881                return false;
    4882            }
    4883            if (!ObjectUtilities.equal(this.domainGridlineStroke,
    4884                    that.domainGridlineStroke)) {
    4885                return false;
    4886            }
    4887            if (!PaintUtilities.equal(this.domainGridlinePaint,
    4888                    that.domainGridlinePaint)) {
    4889                return false;
    4890            }
    4891            if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) {
    4892                return false;
    4893            }
    4894            if (!ObjectUtilities.equal(this.rangeGridlineStroke,
    4895                    that.rangeGridlineStroke)) {
    4896                return false;
    4897            }
    4898            if (!PaintUtilities.equal(this.rangeGridlinePaint,
    4899                    that.rangeGridlinePaint)) {
    4900                return false;
    4901            }
    4902            if (this.anchorValue != that.anchorValue) {
    4903                return false;
    4904            }
    4905            if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) {
    4906                return false;
    4907            }
    4908            if (this.rangeCrosshairValue != that.rangeCrosshairValue) {
    4909                return false;
    4910            }
    4911            if (!ObjectUtilities.equal(this.rangeCrosshairStroke,
    4912                    that.rangeCrosshairStroke)) {
    4913                return false;
    4914            }
    4915            if (!PaintUtilities.equal(this.rangeCrosshairPaint,
    4916                    that.rangeCrosshairPaint)) {
    4917                return false;
    4918            }
    4919            if (this.rangeCrosshairLockedOnData
    4920                    != that.rangeCrosshairLockedOnData) {
    4921                return false;
    4922            }
    4923            if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
    4924                    that.foregroundDomainMarkers)) {
    4925                return false;
    4926            }
    4927            if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
    4928                    that.backgroundDomainMarkers)) {
    4929                return false;
    4930            }
    4931            if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
    4932                    that.foregroundRangeMarkers)) {
    4933                return false;
    4934            }
    4935            if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
    4936                    that.backgroundRangeMarkers)) {
    4937                return false;
    4938            }
    4939            if (!ObjectUtilities.equal(this.annotations, that.annotations)) {
    4940                return false;
    4941            }
    4942            if (this.weight != that.weight) {
    4943                return false;
    4944            }
    4945            if (!ObjectUtilities.equal(this.fixedDomainAxisSpace,
    4946                    that.fixedDomainAxisSpace)) {
    4947                return false;
    4948            }
    4949            if (!ObjectUtilities.equal(this.fixedRangeAxisSpace,
    4950                    that.fixedRangeAxisSpace)) {
    4951                return false;
    4952            }
    4953            if (!ObjectUtilities.equal(this.fixedLegendItems,
    4954                    that.fixedLegendItems)) {
    4955                return false;
    4956            }
    4957            if (this.domainCrosshairVisible != that.domainCrosshairVisible) {
    4958                return false;
    4959            }
    4960            if (this.crosshairDatasetIndex != that.crosshairDatasetIndex) {
    4961                return false;
    4962            }
    4963            if (!ObjectUtilities.equal(this.domainCrosshairColumnKey,
    4964                    that.domainCrosshairColumnKey)) {
    4965                return false;
    4966            }
    4967            if (!ObjectUtilities.equal(this.domainCrosshairRowKey,
    4968                    that.domainCrosshairRowKey)) {
    4969                return false;
    4970            }
    4971            if (!PaintUtilities.equal(this.domainCrosshairPaint,
    4972                    that.domainCrosshairPaint)) {
    4973                return false;
    4974            }
    4975            if (!ObjectUtilities.equal(this.domainCrosshairStroke,
    4976                    that.domainCrosshairStroke)) {
    4977                return false;
    4978            }
    4979            if (this.rangeMinorGridlinesVisible
    4980                    != that.rangeMinorGridlinesVisible) {
    4981                return false;
    4982            }
    4983            if (!PaintUtilities.equal(this.rangeMinorGridlinePaint,
    4984                    that.rangeMinorGridlinePaint)) {
    4985                return false;
    4986            }
    4987            if (!ObjectUtilities.equal(this.rangeMinorGridlineStroke,
    4988                    that.rangeMinorGridlineStroke)) {
    4989                return false;
    4990            }
    4991            if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) {
    4992                return false;
    4993            }
    4994            if (!PaintUtilities.equal(this.rangeZeroBaselinePaint,
    4995                    that.rangeZeroBaselinePaint)) {
    4996                return false;
    4997            }
    4998            if (!ObjectUtilities.equal(this.rangeZeroBaselineStroke,
    4999                    that.rangeZeroBaselineStroke)) {
    5000                return false;
    5001            }
    5002            if (!ObjectUtilities.equal(this.shadowGenerator,
    5003                    that.shadowGenerator)) {
    5004                return false;
    5005            }
    5006            return super.equals(obj);
    5007        }
    5008
    5009        /**
    5010         * Returns a clone of the plot.
    5011         *
    5012         * @return A clone.
    5013         *
    5014         * @throws CloneNotSupportedException  if the cloning is not supported.
    5015         */
    5016        public Object clone() throws CloneNotSupportedException {
    5017
    5018            CategoryPlot clone = (CategoryPlot) super.clone();
    5019
    5020            clone.domainAxes = new ObjectList();
    5021            for (int i = 0; i < this.domainAxes.size(); i++) {
    5022                CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i);
    5023                if (xAxis != null) {
    5024                    CategoryAxis clonedAxis = (CategoryAxis) xAxis.clone();
    5025                    clone.setDomainAxis(i, clonedAxis);
    5026                }
    5027            }
    5028            clone.domainAxisLocations
    5029                    = (ObjectList) this.domainAxisLocations.clone();
    5030
    5031            clone.rangeAxes = new ObjectList();
    5032            for (int i = 0; i < this.rangeAxes.size(); i++) {
    5033                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i);
    5034                if (yAxis != null) {
    5035                    ValueAxis clonedAxis = (ValueAxis) yAxis.clone();
    5036                    clone.setRangeAxis(i, clonedAxis);
    5037                }
    5038            }
    5039            clone.rangeAxisLocations = (ObjectList) this.rangeAxisLocations.clone();
    5040
    5041            clone.datasets = (ObjectList) this.datasets.clone();
    5042            for (int i = 0; i < clone.datasets.size(); i++) {
    5043                CategoryDataset dataset = clone.getDataset(i);
    5044                if (dataset != null) {
    5045                    dataset.addChangeListener(clone);
    5046                }
    5047            }
    5048            clone.datasetToDomainAxesMap = new TreeMap();
    5049            clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap);
    5050            clone.datasetToRangeAxesMap = new TreeMap();
    5051            clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap);
    5052
    5053            clone.renderers = (ObjectList) this.renderers.clone();
    5054            for (int i = 0; i < this.renderers.size(); i++) {
    5055                CategoryItemRenderer renderer2 = (CategoryItemRenderer)
    5056                        this.renderers.get(i);
    5057                if (renderer2 instanceof PublicCloneable) {
    5058                    PublicCloneable pc = (PublicCloneable) renderer2;
    5059                    CategoryItemRenderer rc = (CategoryItemRenderer) pc.clone();
    5060                    clone.renderers.set(i, rc);
    5061                    rc.setPlot(clone);
    5062                    rc.addChangeListener(clone);
    5063                }
    5064            }
    5065            if (this.fixedDomainAxisSpace != null) {
    5066                clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtilities.clone(
    5067                        this.fixedDomainAxisSpace);
    5068            }
    5069            if (this.fixedRangeAxisSpace != null) {
    5070                clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtilities.clone(
    5071                        this.fixedRangeAxisSpace);
    5072            }
    5073
    5074            clone.annotations = (List) ObjectUtilities.deepClone(this.annotations);
    5075            clone.foregroundDomainMarkers = cloneMarkerMap(
    5076                    this.foregroundDomainMarkers);
    5077            clone.backgroundDomainMarkers = cloneMarkerMap(
    5078                    this.backgroundDomainMarkers);
    5079            clone.foregroundRangeMarkers = cloneMarkerMap(
    5080                    this.foregroundRangeMarkers);
    5081            clone.backgroundRangeMarkers = cloneMarkerMap(
    5082                    this.backgroundRangeMarkers);
    5083            if (this.fixedLegendItems != null) {
    5084                clone.fixedLegendItems
    5085                        = (LegendItemCollection) this.fixedLegendItems.clone();
    5086            }
    5087            return clone;
    5088
    5089        }
    5090
    5091        /**
    5092         * A utility method to clone the marker maps.
    5093         *
    5094         * @param map  the map to clone.
    5095         *
    5096         * @return A clone of the map.
    5097         *
    5098         * @throws CloneNotSupportedException if there is some problem cloning the
    5099         *                                    map.
    5100         */
    5101        private Map cloneMarkerMap(Map map) throws CloneNotSupportedException {
    5102            Map clone = new HashMap();
    5103            Set keys = map.keySet();
    5104            Iterator iterator = keys.iterator();
    5105            while (iterator.hasNext()) {
    5106                Object key = iterator.next();
    5107                List entry = (List) map.get(key);
    5108                Object toAdd = ObjectUtilities.deepClone(entry);
    5109                clone.put(key, toAdd);
    5110            }
    5111            return clone;
    5112        }
    5113
    5114        /**
    5115         * Provides serialization support.
    5116         *
    5117         * @param stream  the output stream.
    5118         *
    5119         * @throws IOException  if there is an I/O error.
    5120         */
    5121        private void writeObject(ObjectOutputStream stream) throws IOException {
    5122            stream.defaultWriteObject();
    5123            SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
    5124            SerialUtilities.writePaint(this.domainGridlinePaint, stream);
    5125            SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
    5126            SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
    5127            SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream);
    5128            SerialUtilities.writePaint(this.rangeCrosshairPaint, stream);
    5129            SerialUtilities.writeStroke(this.domainCrosshairStroke, stream);
    5130            SerialUtilities.writePaint(this.domainCrosshairPaint, stream);
    5131            SerialUtilities.writeStroke(this.rangeMinorGridlineStroke, stream);
    5132            SerialUtilities.writePaint(this.rangeMinorGridlinePaint, stream);
    5133            SerialUtilities.writeStroke(this.rangeZeroBaselineStroke, stream);
    5134            SerialUtilities.writePaint(this.rangeZeroBaselinePaint, stream);
    5135        }
    5136
    5137        /**
    5138         * Provides serialization support.
    5139         *
    5140         * @param stream  the input stream.
    5141         *
    5142         * @throws IOException  if there is an I/O error.
    5143         * @throws ClassNotFoundException  if there is a classpath problem.
    5144         */
    5145        private void readObject(ObjectInputStream stream)
    5146            throws IOException, ClassNotFoundException {
    5147
    5148            stream.defaultReadObject();
    5149            this.domainGridlineStroke = SerialUtilities.readStroke(stream);
    5150            this.domainGridlinePaint = SerialUtilities.readPaint(stream);
    5151            this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
    5152            this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
    5153            this.rangeCrosshairStroke = SerialUtilities.readStroke(stream);
    5154            this.rangeCrosshairPaint = SerialUtilities.readPaint(stream);
    5155            this.domainCrosshairStroke = SerialUtilities.readStroke(stream);
    5156            this.domainCrosshairPaint = SerialUtilities.readPaint(stream);
    5157            this.rangeMinorGridlineStroke = SerialUtilities.readStroke(stream);
    5158            this.rangeMinorGridlinePaint = SerialUtilities.readPaint(stream);
    5159            this.rangeZeroBaselineStroke = SerialUtilities.readStroke(stream);
    5160            this.rangeZeroBaselinePaint = SerialUtilities.readPaint(stream);
    5161
    5162            for (int i = 0; i < this.domainAxes.size(); i++) {
    5163                CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i);
    5164                if (xAxis != null) {
    5165                    xAxis.setPlot(this);
    5166                    xAxis.addChangeListener(this);
    5167                }
    5168            }
    5169            for (int i = 0; i < this.rangeAxes.size(); i++) {
    5170                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i);
    5171                if (yAxis != null) {
    5172                    yAxis.setPlot(this);
    5173                    yAxis.addChangeListener(this);
    5174                }
    5175            }
    5176            int datasetCount = this.datasets.size();
    5177            for (int i = 0; i < datasetCount; i++) {
    5178                Dataset dataset = (Dataset) this.datasets.get(i);
    5179                if (dataset != null) {
    5180                    dataset.addChangeListener(this);
    5181                }
    5182            }
    5183            int rendererCount = this.renderers.size();
    5184            for (int i = 0; i < rendererCount; i++) {
    5185                CategoryItemRenderer renderer
    5186                    = (CategoryItemRenderer) this.renderers.get(i);
    5187                if (renderer != null) {
    5188                    renderer.addChangeListener(this);
    5189                }
    5190            }
    5191
    5192        }
    5193
    5194    }