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