    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
    184package org.jfree.chart.plot;
    185
    186import java.awt.AlphaComposite;
    187import java.awt.BasicStroke;
    188import java.awt.Color;
    189import java.awt.Composite;
    190import java.awt.Font;
    191import java.awt.Graphics2D;
    192import java.awt.Paint;
    193import java.awt.Rectangle;
    194import java.awt.Shape;
    195import java.awt.Stroke;
    196import java.awt.geom.Line2D;
    197import java.awt.geom.Point2D;
    198import java.awt.geom.Rectangle2D;
    199import java.awt.image.BufferedImage;
    200import java.io.IOException;
    201import java.io.ObjectInputStream;
    202import java.io.ObjectOutputStream;
    203import java.io.Serializable;
    204import java.util.ArrayList;
    205import java.util.Collection;
    206import java.util.Collections;
    207import java.util.HashMap;
    208import java.util.HashSet;
    209import java.util.Iterator;
    210import java.util.List;
    211import java.util.Map;
    212import java.util.ResourceBundle;
    213import java.util.Set;
    214import java.util.TreeMap;
    215import org.jfree.chart.LegendItemCollection;
    216import org.jfree.chart.annotations.Annotation;
    217import org.jfree.chart.annotations.CategoryAnnotation;
    218import org.jfree.chart.axis.Axis;
    219import org.jfree.chart.axis.AxisCollection;
    220import org.jfree.chart.axis.AxisLocation;
    221import org.jfree.chart.axis.AxisSpace;
    222import org.jfree.chart.axis.AxisState;
    223import org.jfree.chart.axis.CategoryAnchor;
    224import org.jfree.chart.axis.CategoryAxis;
    225import org.jfree.chart.axis.TickType;
    226import org.jfree.chart.axis.ValueAxis;
    227import org.jfree.chart.axis.ValueTick;
    228import org.jfree.chart.event.AnnotationChangeEvent;
    229import org.jfree.chart.event.AnnotationChangeListener;
    230import org.jfree.chart.event.ChartChangeEventType;
    231import org.jfree.chart.event.PlotChangeEvent;
    232import org.jfree.chart.event.RendererChangeEvent;
    233import org.jfree.chart.event.RendererChangeListener;
    234import org.jfree.chart.renderer.category.AbstractCategoryItemRenderer;
    235import org.jfree.chart.renderer.category.CategoryItemRenderer;
    236import org.jfree.chart.renderer.category.CategoryItemRendererState;
    237import org.jfree.chart.util.ResourceBundleWrapper;
    238import org.jfree.chart.util.ShadowGenerator;
    239import org.jfree.data.Range;
    240import org.jfree.data.category.CategoryDataset;
    241import org.jfree.data.general.Dataset;
    242import org.jfree.data.general.DatasetChangeEvent;
    243import org.jfree.data.general.DatasetUtilities;
    244import org.jfree.io.SerialUtilities;
    245import org.jfree.ui.Layer;
    246import org.jfree.ui.RectangleEdge;
    247import org.jfree.ui.RectangleInsets;
    248import org.jfree.util.ObjectList;
    249import org.jfree.util.ObjectUtilities;
    250import org.jfree.util.PaintUtilities;
    251import org.jfree.util.PublicCloneable;
    252import org.jfree.util.ShapeUtilities;
    253import 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 */
    259public 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}