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