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