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