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