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