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