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