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