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