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