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.29 2007/06/07 12:49:36 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     * 04-May-2007 : Render only visible data items if possible (DG);
187     * 24-May-2007 : Fixed bug in render method for an empty series (DG);
188     * 07-Jun-2007 : Modified drawBackground() to pass orientation to 
189     *               fillBackground() for handling GradientPaint (DG);
190     *
191     */
192    
193    package org.jfree.chart.plot;
194    
195    import java.awt.AlphaComposite;
196    import java.awt.BasicStroke;
197    import java.awt.Color;
198    import java.awt.Composite;
199    import java.awt.Graphics2D;
200    import java.awt.Paint;
201    import java.awt.Shape;
202    import java.awt.Stroke;
203    import java.awt.geom.Line2D;
204    import java.awt.geom.Point2D;
205    import java.awt.geom.Rectangle2D;
206    import java.io.IOException;
207    import java.io.ObjectInputStream;
208    import java.io.ObjectOutputStream;
209    import java.io.Serializable;
210    import java.util.ArrayList;
211    import java.util.Collection;
212    import java.util.Collections;
213    import java.util.HashMap;
214    import java.util.Iterator;
215    import java.util.List;
216    import java.util.Map;
217    import java.util.ResourceBundle;
218    import java.util.Set;
219    import java.util.TreeMap;
220    
221    import org.jfree.chart.LegendItem;
222    import org.jfree.chart.LegendItemCollection;
223    import org.jfree.chart.annotations.XYAnnotation;
224    import org.jfree.chart.axis.Axis;
225    import org.jfree.chart.axis.AxisCollection;
226    import org.jfree.chart.axis.AxisLocation;
227    import org.jfree.chart.axis.AxisSpace;
228    import org.jfree.chart.axis.AxisState;
229    import org.jfree.chart.axis.ValueAxis;
230    import org.jfree.chart.axis.ValueTick;
231    import org.jfree.chart.event.ChartChangeEventType;
232    import org.jfree.chart.event.PlotChangeEvent;
233    import org.jfree.chart.event.RendererChangeEvent;
234    import org.jfree.chart.event.RendererChangeListener;
235    import org.jfree.chart.renderer.RendererUtilities;
236    import org.jfree.chart.renderer.xy.AbstractXYItemRenderer;
237    import org.jfree.chart.renderer.xy.XYItemRenderer;
238    import org.jfree.chart.renderer.xy.XYItemRendererState;
239    import org.jfree.data.Range;
240    import org.jfree.data.general.Dataset;
241    import org.jfree.data.general.DatasetChangeEvent;
242    import org.jfree.data.general.DatasetUtilities;
243    import org.jfree.data.xy.XYDataset;
244    import org.jfree.io.SerialUtilities;
245    import org.jfree.ui.Layer;
246    import org.jfree.ui.RectangleEdge;
247    import org.jfree.ui.RectangleInsets;
248    import org.jfree.util.ObjectList;
249    import org.jfree.util.ObjectUtilities;
250    import org.jfree.util.PaintUtilities;
251    import org.jfree.util.PublicCloneable;
252    
253    /**
254     * A general class for plotting data in the form of (x, y) pairs.  This plot can
255     * use data from any class that implements the {@link XYDataset} interface.
256     * <P>
257     * <code>XYPlot</code> makes use of an {@link XYItemRenderer} to draw each point
258     * on the plot.  By using different renderers, various chart types can be
259     * produced.
260     * <p>
261     * The {@link org.jfree.chart.ChartFactory} class contains static methods for
262     * creating pre-configured charts.
263     */
264    public class XYPlot extends Plot implements ValueAxisPlot,
265                                                Zoomable,
266                                                RendererChangeListener,
267                                                Cloneable, PublicCloneable,
268                                                Serializable {
269    
270        /** For serialization. */
271        private static final long serialVersionUID = 7044148245716569264L;
272        
273        /** The default grid line stroke. */
274        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
275                BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, 
276                new float[] {2.0f, 2.0f}, 0.0f);
277    
278        /** The default grid line paint. */
279        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
280    
281        /** The default crosshair visibility. */
282        public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
283    
284        /** The default crosshair stroke. */
285        public static final Stroke DEFAULT_CROSSHAIR_STROKE
286                = DEFAULT_GRIDLINE_STROKE;
287    
288        /** The default crosshair paint. */
289        public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
290    
291        /** The resourceBundle for the localization. */
292        protected static ResourceBundle localizationResources 
293                = ResourceBundle.getBundle(
294                        "org.jfree.chart.plot.LocalizationBundle");
295    
296        /** The plot orientation. */
297        private PlotOrientation orientation;
298    
299        /** The offset between the data area and the axes. */
300        private RectangleInsets axisOffset;
301    
302        /** The domain axis / axes (used for the x-values). */
303        private ObjectList domainAxes;
304    
305        /** The domain axis locations. */
306        private ObjectList domainAxisLocations;
307    
308        /** The range axis (used for the y-values). */
309        private ObjectList rangeAxes;
310    
311        /** The range axis location. */
312        private ObjectList rangeAxisLocations;
313    
314        /** Storage for the datasets. */
315        private ObjectList datasets;
316    
317        /** Storage for the renderers. */
318        private ObjectList renderers;
319    
320        /**
321         * Storage for keys that map datasets/renderers to domain axes.  If the
322         * map contains no entry for a dataset, it is assumed to map to the
323         * primary domain axis (index = 0).
324         */
325        private Map datasetToDomainAxisMap;
326    
327        /**
328         * Storage for keys that map datasets/renderers to range axes. If the
329         * map contains no entry for a dataset, it is assumed to map to the
330         * primary domain axis (index = 0).
331         */
332        private Map datasetToRangeAxisMap;
333    
334        /** The origin point for the quadrants (if drawn). */
335        private transient Point2D quadrantOrigin = new Point2D.Double(0.0, 0.0);
336    
337        /** The paint used for each quadrant. */
338        private transient Paint[] quadrantPaint
339                = new Paint[] {null, null, null, null};
340    
341        /** A flag that controls whether the domain grid-lines are visible. */
342        private boolean domainGridlinesVisible;
343    
344        /** The stroke used to draw the domain grid-lines. */
345        private transient Stroke domainGridlineStroke;
346    
347        /** The paint used to draw the domain grid-lines. */
348        private transient Paint domainGridlinePaint;
349    
350        /** A flag that controls whether the range grid-lines are visible. */
351        private boolean rangeGridlinesVisible;
352    
353        /** The stroke used to draw the range grid-lines. */
354        private transient Stroke rangeGridlineStroke;
355    
356        /** The paint used to draw the range grid-lines. */
357        private transient Paint rangeGridlinePaint;
358    
359        /** 
360         * A flag that controls whether or not the zero baseline against the domain
361         * axis is visible.
362         * 
363         * @since 1.0.5
364         */
365        private boolean domainZeroBaselineVisible;
366    
367        /** 
368         * The stroke used for the zero baseline against the domain axis. 
369         * 
370         * @since 1.0.5
371         */
372        private transient Stroke domainZeroBaselineStroke;
373    
374        /** 
375         * The paint used for the zero baseline against the domain axis. 
376         * 
377         * @since 1.0.5
378         */
379        private transient Paint domainZeroBaselinePaint;
380    
381        /** 
382         * A flag that controls whether or not the zero baseline against the range
383         * axis is visible.
384         */
385        private boolean rangeZeroBaselineVisible;
386    
387        /** The stroke used for the zero baseline against the range axis. */
388        private transient Stroke rangeZeroBaselineStroke;
389    
390        /** The paint used for the zero baseline against the range axis. */
391        private transient Paint rangeZeroBaselinePaint;
392    
393        /** A flag that controls whether or not a domain crosshair is drawn..*/
394        private boolean domainCrosshairVisible;
395    
396        /** The domain crosshair value. */
397        private double domainCrosshairValue;
398    
399        /** The pen/brush used to draw the crosshair (if any). */
400        private transient Stroke domainCrosshairStroke;
401    
402        /** The color used to draw the crosshair (if any). */
403        private transient Paint domainCrosshairPaint;
404    
405        /**
406         * A flag that controls whether or not the crosshair locks onto actual
407         * data points.
408         */
409        private boolean domainCrosshairLockedOnData = true;
410    
411        /** A flag that controls whether or not a range crosshair is drawn..*/
412        private boolean rangeCrosshairVisible;
413    
414        /** The range crosshair value. */
415        private double rangeCrosshairValue;
416    
417        /** The pen/brush used to draw the crosshair (if any). */
418        private transient Stroke rangeCrosshairStroke;
419    
420        /** The color used to draw the crosshair (if any). */
421        private transient Paint rangeCrosshairPaint;
422    
423        /**
424         * A flag that controls whether or not the crosshair locks onto actual
425         * data points.
426         */
427        private boolean rangeCrosshairLockedOnData = true;
428    
429        /** A map of lists of foreground markers (optional) for the domain axes. */
430        private Map foregroundDomainMarkers;
431    
432        /** A map of lists of background markers (optional) for the domain axes. */
433        private Map backgroundDomainMarkers;
434    
435        /** A map of lists of foreground markers (optional) for the range axes. */
436        private Map foregroundRangeMarkers;
437    
438        /** A map of lists of background markers (optional) for the range axes. */
439        private Map backgroundRangeMarkers;
440    
441        /** 
442         * A (possibly empty) list of annotations for the plot.  The list should
443         * be initialised in the constructor and never allowed to be 
444         * <code>null</code>.
445         */
446        private List annotations;
447    
448        /** The paint used for the domain tick bands (if any). */
449        private transient Paint domainTickBandPaint;
450    
451        /** The paint used for the range tick bands (if any). */
452        private transient Paint rangeTickBandPaint;
453    
454        /** The fixed domain axis space. */
455        private AxisSpace fixedDomainAxisSpace;
456    
457        /** The fixed range axis space. */
458        private AxisSpace fixedRangeAxisSpace;
459    
460        /**
461         * The order of the dataset rendering (REVERSE draws the primary dataset
462         * last so that it appears to be on top).
463         */
464        private DatasetRenderingOrder datasetRenderingOrder
465                = DatasetRenderingOrder.REVERSE;
466    
467        /**
468         * The order of the series rendering (REVERSE draws the primary series
469         * last so that it appears to be on top).
470         */
471        private SeriesRenderingOrder seriesRenderingOrder
472                = SeriesRenderingOrder.REVERSE;
473    
474        /**
475         * The weight for this plot (only relevant if this is a subplot in a
476         * combined plot).
477         */
478        private int weight;
479    
480        /**
481         * An optional collection of legend items that can be returned by the
482         * getLegendItems() method.
483         */
484        private LegendItemCollection fixedLegendItems;
485    
486        /**
487         * Creates a new <code>XYPlot</code> instance with no dataset, no axes and
488         * no renderer.  You should specify these items before using the plot.
489         */
490        public XYPlot() {
491            this(null, null, null, null);
492        }
493    
494        /**
495         * Creates a new plot with the specified dataset, axes and renderer.  Any
496         * of the arguments can be <code>null</code>, but in that case you should
497         * take care to specify the value before using the plot (otherwise a
498         * <code>NullPointerException</code> may be thrown).
499         *
500         * @param dataset  the dataset (<code>null</code> permitted).
501         * @param domainAxis  the domain axis (<code>null</code> permitted).
502         * @param rangeAxis  the range axis (<code>null</code> permitted).
503         * @param renderer  the renderer (<code>null</code> permitted).
504         */
505        public XYPlot(XYDataset dataset,
506                      ValueAxis domainAxis,
507                      ValueAxis rangeAxis,
508                      XYItemRenderer renderer) {
509    
510            super();
511    
512            this.orientation = PlotOrientation.VERTICAL;
513            this.weight = 1;  // only relevant when this is a subplot
514            this.axisOffset = RectangleInsets.ZERO_INSETS;
515    
516            // allocate storage for datasets, axes and renderers (all optional)
517            this.domainAxes = new ObjectList();
518            this.domainAxisLocations = new ObjectList();
519            this.foregroundDomainMarkers = new HashMap();
520            this.backgroundDomainMarkers = new HashMap();
521    
522            this.rangeAxes = new ObjectList();
523            this.rangeAxisLocations = new ObjectList();
524            this.foregroundRangeMarkers = new HashMap();
525            this.backgroundRangeMarkers = new HashMap();
526    
527            this.datasets = new ObjectList();
528            this.renderers = new ObjectList();
529    
530            this.datasetToDomainAxisMap = new TreeMap();
531            this.datasetToRangeAxisMap = new TreeMap();
532    
533            this.datasets.set(0, dataset);
534            if (dataset != null) {
535                dataset.addChangeListener(this);
536            }
537    
538            this.renderers.set(0, renderer);
539            if (renderer != null) {
540                renderer.setPlot(this);
541                renderer.addChangeListener(this);
542            }
543    
544            this.domainAxes.set(0, domainAxis);
545            this.mapDatasetToDomainAxis(0, 0);
546            if (domainAxis != null) {
547                domainAxis.setPlot(this);
548                domainAxis.addChangeListener(this);
549            }
550            this.domainAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT);
551    
552            this.rangeAxes.set(0, rangeAxis);
553            this.mapDatasetToRangeAxis(0, 0);
554            if (rangeAxis != null) {
555                rangeAxis.setPlot(this);
556                rangeAxis.addChangeListener(this);
557            }
558            this.rangeAxisLocations.set(0, AxisLocation.BOTTOM_OR_LEFT);
559    
560            configureDomainAxes();
561            configureRangeAxes();
562    
563            this.domainGridlinesVisible = true;
564            this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
565            this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
566    
567            this.domainZeroBaselineVisible = false;
568            this.domainZeroBaselinePaint = Color.black;
569            this.domainZeroBaselineStroke = new BasicStroke(0.5f);
570    
571            this.rangeGridlinesVisible = true;
572            this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
573            this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
574    
575            this.rangeZeroBaselineVisible = false;
576            this.rangeZeroBaselinePaint = Color.black;
577            this.rangeZeroBaselineStroke = new BasicStroke(0.5f);
578    
579            this.domainCrosshairVisible = false;
580            this.domainCrosshairValue = 0.0;
581            this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
582            this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
583    
584            this.rangeCrosshairVisible = false;
585            this.rangeCrosshairValue = 0.0;
586            this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
587            this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
588    
589            this.annotations = new java.util.ArrayList();
590    
591        }
592    
593        /**
594         * Returns the plot type as a string.
595         *
596         * @return A short string describing the type of plot.
597         */
598        public String getPlotType() {
599            return localizationResources.getString("XY_Plot");
600        }
601    
602        /**
603         * Returns the orientation of the plot.
604         *
605         * @return The orientation (never <code>null</code>).
606         * 
607         * @see #setOrientation(PlotOrientation)
608         */
609        public PlotOrientation getOrientation() {
610            return this.orientation;
611        }
612    
613        /**
614         * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
615         * all registered listeners.
616         *
617         * @param orientation  the orientation (<code>null</code> not allowed).
618         * 
619         * @see #getOrientation()
620         */
621        public void setOrientation(PlotOrientation orientation) {
622            if (orientation == null) {
623                throw new IllegalArgumentException("Null 'orientation' argument.");
624            }
625            if (orientation != this.orientation) {
626                this.orientation = orientation;
627                notifyListeners(new PlotChangeEvent(this));
628            }
629        }
630    
631        /**
632         * Returns the axis offset.
633         *
634         * @return The axis offset (never <code>null</code>).
635         * 
636         * @see #setAxisOffset(RectangleInsets)
637         */
638        public RectangleInsets getAxisOffset() {
639            return this.axisOffset;
640        }
641    
642        /**
643         * Sets the axis offsets (gap between the data area and the axes) and sends
644         * a {@link PlotChangeEvent} to all registered listeners.
645         *
646         * @param offset  the offset (<code>null</code> not permitted).
647         * 
648         * @see #getAxisOffset()
649         */
650        public void setAxisOffset(RectangleInsets offset) {
651            if (offset == null) {
652                throw new IllegalArgumentException("Null 'offset' argument.");
653            }
654            this.axisOffset = offset;
655            notifyListeners(new PlotChangeEvent(this));
656        }
657    
658        /**
659         * Returns the domain axis with index 0.  If the domain axis for this plot
660         * is <code>null</code>, then the method will return the parent plot's 
661         * domain axis (if there is a parent plot).
662         *
663         * @return The domain axis (possibly <code>null</code>).
664         * 
665         * @see #getDomainAxis(int)
666         * @see #setDomainAxis(ValueAxis)
667         */
668        public ValueAxis getDomainAxis() {
669            return getDomainAxis(0);
670        }
671    
672        /**
673         * Returns the domain axis with the specified index, or <code>null</code>.
674         *
675         * @param index  the axis index.
676         *
677         * @return The axis (<code>null</code> possible).
678         * 
679         * @see #setDomainAxis(int, ValueAxis)
680         */
681        public ValueAxis getDomainAxis(int index) {
682            ValueAxis result = null;
683            if (index < this.domainAxes.size()) {
684                result = (ValueAxis) this.domainAxes.get(index);
685            }
686            if (result == null) {
687                Plot parent = getParent();
688                if (parent instanceof XYPlot) {
689                    XYPlot xy = (XYPlot) parent;
690                    result = xy.getDomainAxis(index);
691                }
692            }
693            return result;
694        }
695    
696        /**
697         * Sets the domain axis for the plot and sends a {@link PlotChangeEvent}
698         * to all registered listeners.
699         *
700         * @param axis  the new axis (<code>null</code> permitted).
701         * 
702         * @see #getDomainAxis()
703         * @see #setDomainAxis(int, ValueAxis)
704         */
705        public void setDomainAxis(ValueAxis axis) {
706            setDomainAxis(0, axis);
707        }
708    
709        /**
710         * Sets a domain axis and sends a {@link PlotChangeEvent} to all
711         * registered listeners.
712         *
713         * @param index  the axis index.
714         * @param axis  the axis (<code>null</code> permitted).
715         * 
716         * @see #getDomainAxis(int)
717         * @see #setRangeAxis(int, ValueAxis)
718         */
719        public void setDomainAxis(int index, ValueAxis axis) {
720            setDomainAxis(index, axis, true);
721        }
722        
723        /**
724         * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
725         * all registered listeners.
726         *
727         * @param index  the axis index.
728         * @param axis  the axis.
729         * @param notify  notify listeners?
730         * 
731         * @see #getDomainAxis(int)
732         */
733        public void setDomainAxis(int index, ValueAxis axis, boolean notify) {
734            ValueAxis existing = getDomainAxis(index);
735            if (existing != null) {
736                existing.removeChangeListener(this);
737            }
738            if (axis != null) {
739                axis.setPlot(this);
740            }
741            this.domainAxes.set(index, axis);
742            if (axis != null) {
743                axis.configure();
744                axis.addChangeListener(this);
745            }
746            if (notify) {
747                notifyListeners(new PlotChangeEvent(this));
748            }
749        }
750    
751        /**
752         * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
753         * to all registered listeners.
754         * 
755         * @param axes  the axes (<code>null</code> not permitted).
756         * 
757         * @see #setRangeAxes(ValueAxis[])
758         */
759        public void setDomainAxes(ValueAxis[] axes) {
760            for (int i = 0; i < axes.length; i++) {
761                setDomainAxis(i, axes[i], false);   
762            }
763            notifyListeners(new PlotChangeEvent(this));
764        }
765        
766        /**
767         * Returns the location of the primary domain axis.
768         *
769         * @return The location (never <code>null</code>).
770         * 
771         * @see #setDomainAxisLocation(AxisLocation)
772         */
773        public AxisLocation getDomainAxisLocation() {
774            return (AxisLocation) this.domainAxisLocations.get(0);
775        }
776    
777        /**
778         * Sets the location of the primary domain axis and sends a 
779         * {@link PlotChangeEvent} to all registered listeners.
780         *
781         * @param location  the location (<code>null</code> not permitted).
782         * 
783         * @see #getDomainAxisLocation()
784         */
785        public void setDomainAxisLocation(AxisLocation location) {
786            // delegate...
787            setDomainAxisLocation(0, location, true);
788        }
789    
790        /**
791         * Sets the location of the domain axis and, if requested, sends a
792         * {@link PlotChangeEvent} to all registered listeners.
793         *
794         * @param location  the location (<code>null</code> not permitted).
795         * @param notify  notify listeners?
796         * 
797         * @see #getDomainAxisLocation()
798         */
799        public void setDomainAxisLocation(AxisLocation location, boolean notify) {
800            // delegate...
801            setDomainAxisLocation(0, location, notify);
802        }
803    
804        /**
805         * Returns the edge for the primary domain axis (taking into account the
806         * plot's orientation).
807         *
808         * @return The edge.
809         * 
810         * @see #getDomainAxisLocation()
811         * @see #getOrientation()
812         */
813        public RectangleEdge getDomainAxisEdge() {
814            return Plot.resolveDomainAxisLocation(getDomainAxisLocation(), 
815                    this.orientation);
816        }
817    
818        /**
819         * Returns the number of domain axes.
820         *
821         * @return The axis count.
822         * 
823         * @see #getRangeAxisCount()
824         */
825        public int getDomainAxisCount() {
826            return this.domainAxes.size();
827        }
828    
829        /**
830         * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
831         * to all registered listeners.
832         * 
833         * @see #clearRangeAxes()
834         */
835        public void clearDomainAxes() {
836            for (int i = 0; i < this.domainAxes.size(); i++) {
837                ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
838                if (axis != null) {
839                    axis.removeChangeListener(this);
840                }
841            }
842            this.domainAxes.clear();
843            notifyListeners(new PlotChangeEvent(this));
844        }
845    
846        /**
847         * Configures the domain axes. 
848         */
849        public void configureDomainAxes() {
850            for (int i = 0; i < this.domainAxes.size(); i++) {
851                ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
852                if (axis != null) {
853                    axis.configure();
854                }
855            }
856        }
857    
858        /**
859         * Returns the location for a domain axis.  If this hasn't been set
860         * explicitly, the method returns the location that is opposite to the
861         * primary domain axis location.
862         *
863         * @param index  the axis index.
864         *
865         * @return The location (never <code>null</code>).
866         * 
867         * @see #setDomainAxisLocation(int, AxisLocation)
868         */
869        public AxisLocation getDomainAxisLocation(int index) {
870            AxisLocation result = null;
871            if (index < this.domainAxisLocations.size()) {
872                result = (AxisLocation) this.domainAxisLocations.get(index);
873            }
874            if (result == null) {
875                result = AxisLocation.getOpposite(getDomainAxisLocation());
876            }
877            return result;
878        }
879    
880        /**
881         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
882         * to all registered listeners.
883         *
884         * @param index  the axis index.
885         * @param location  the location (<code>null</code> not permitted for index
886         *     0).
887         * 
888         * @see #getDomainAxisLocation(int)
889         */
890        public void setDomainAxisLocation(int index, AxisLocation location) {
891            // delegate...
892            setDomainAxisLocation(index, location, true);
893        }
894    
895        /**
896         * Sets the axis location for a domain axis and, if requested, sends a
897         * {@link PlotChangeEvent} to all registered listeners.
898         * 
899         * @param index  the axis index.
900         * @param location  the location (<code>null</code> not permitted for 
901         *     index 0).
902         * @param notify  notify listeners?
903         * 
904         * @since 1.0.5
905         * 
906         * @see #getDomainAxisLocation(int)
907         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
908         */
909        public void setDomainAxisLocation(int index, AxisLocation location, 
910                boolean notify) {
911            
912            if (index == 0 && location == null) {
913                throw new IllegalArgumentException(
914                        "Null 'location' for index 0 not permitted.");
915            }
916            this.domainAxisLocations.set(index, location);
917            if (notify) {
918                notifyListeners(new PlotChangeEvent(this));
919            }        
920        }
921    
922        /**
923         * Returns the edge for a domain axis.
924         *
925         * @param index  the axis index.
926         *
927         * @return The edge.
928         * 
929         * @see #getRangeAxisEdge(int)
930         */
931        public RectangleEdge getDomainAxisEdge(int index) {
932            AxisLocation location = getDomainAxisLocation(index);
933            RectangleEdge result = Plot.resolveDomainAxisLocation(location, 
934                    this.orientation);
935            if (result == null) {
936                result = RectangleEdge.opposite(getDomainAxisEdge());
937            }
938            return result;
939        }
940    
941        /**
942         * Returns the range axis for the plot.  If the range axis for this plot is
943         * <code>null</code>, then the method will return the parent plot's range 
944         * axis (if there is a parent plot).
945         *
946         * @return The range axis.
947         * 
948         * @see #getRangeAxis(int)
949         * @see #setRangeAxis(ValueAxis)
950         */
951        public ValueAxis getRangeAxis() {
952            return getRangeAxis(0);
953        }
954    
955        /**
956         * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
957         * all registered listeners.
958         *
959         * @param axis  the axis (<code>null</code> permitted).
960         *
961         * @see #getRangeAxis()
962         * @see #setRangeAxis(int, ValueAxis)
963         */
964        public void setRangeAxis(ValueAxis axis)  {
965    
966            if (axis != null) {
967                axis.setPlot(this);
968            }
969    
970            // plot is likely registered as a listener with the existing axis...
971            ValueAxis existing = getRangeAxis();
972            if (existing != null) {
973                existing.removeChangeListener(this);
974            }
975    
976            this.rangeAxes.set(0, axis);
977            if (axis != null) {
978                axis.configure();
979                axis.addChangeListener(this);
980            }
981            notifyListeners(new PlotChangeEvent(this));
982    
983        }
984    
985        /**
986         * Returns the location of the primary range axis.
987         *
988         * @return The location (never <code>null</code>).
989         * 
990         * @see #setRangeAxisLocation(AxisLocation)
991         */
992        public AxisLocation getRangeAxisLocation() {
993            return (AxisLocation) this.rangeAxisLocations.get(0);
994        }
995    
996        /**
997         * Sets the location of the primary range axis and sends a
998         * {@link PlotChangeEvent} to all registered listeners.
999         *
1000         * @param location  the location (<code>null</code> not permitted).
1001         * 
1002         * @see #getRangeAxisLocation()
1003         */
1004        public void setRangeAxisLocation(AxisLocation location) {
1005            // delegate...
1006            setRangeAxisLocation(0, location, true);
1007        }
1008    
1009        /**
1010         * Sets the location of the primary range axis and, if requested, sends a
1011         * {@link PlotChangeEvent} to all registered listeners.
1012         *
1013         * @param location  the location (<code>null</code> not permitted).
1014         * @param notify  notify listeners?
1015         * 
1016         * @see #getRangeAxisLocation()
1017         */
1018        public void setRangeAxisLocation(AxisLocation location, boolean notify) {
1019            // delegate...
1020            setRangeAxisLocation(0, location, notify);
1021        }
1022    
1023        /**
1024         * Returns the edge for the primary range axis.
1025         *
1026         * @return The range axis edge.
1027         * 
1028         * @see #getRangeAxisLocation()
1029         * @see #getOrientation()
1030         */
1031        public RectangleEdge getRangeAxisEdge() {
1032            return Plot.resolveRangeAxisLocation(getRangeAxisLocation(), 
1033                    this.orientation);
1034        }
1035    
1036        /**
1037         * Returns a range axis.
1038         *
1039         * @param index  the axis index.
1040         *
1041         * @return The axis (<code>null</code> possible).
1042         * 
1043         * @see #setRangeAxis(int, ValueAxis)
1044         */
1045        public ValueAxis getRangeAxis(int index) {
1046            ValueAxis result = null;
1047            if (index < this.rangeAxes.size()) {
1048                result = (ValueAxis) this.rangeAxes.get(index);
1049            }
1050            if (result == null) {
1051                Plot parent = getParent();
1052                if (parent instanceof XYPlot) {
1053                    XYPlot xy = (XYPlot) parent;
1054                    result = xy.getRangeAxis(index);
1055                }
1056            }
1057            return result;
1058        }
1059    
1060        /**
1061         * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
1062         * listeners.
1063         *
1064         * @param index  the axis index.
1065         * @param axis  the axis (<code>null</code> permitted).
1066         * 
1067         * @see #getRangeAxis(int)
1068         */
1069        public void setRangeAxis(int index, ValueAxis axis) {
1070            setRangeAxis(index, axis, true);
1071        } 
1072        
1073        /**
1074         * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to 
1075         * all registered listeners.
1076         *
1077         * @param index  the axis index.
1078         * @param axis  the axis (<code>null</code> permitted).
1079         * @param notify  notify listeners?
1080         * 
1081         * @see #getRangeAxis(int)
1082         */
1083        public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
1084            ValueAxis existing = getRangeAxis(index);
1085            if (existing != null) {
1086                existing.removeChangeListener(this);
1087            }
1088            if (axis != null) {
1089                axis.setPlot(this);
1090            }
1091            this.rangeAxes.set(index, axis);
1092            if (axis != null) {
1093                axis.configure();
1094                axis.addChangeListener(this);
1095            }
1096            if (notify) {
1097                notifyListeners(new PlotChangeEvent(this));
1098            }
1099        }
1100    
1101        /**
1102         * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
1103         * to all registered listeners.
1104         * 
1105         * @param axes  the axes (<code>null</code> not permitted).
1106         * 
1107         * @see #setDomainAxes(ValueAxis[])
1108         */
1109        public void setRangeAxes(ValueAxis[] axes) {
1110            for (int i = 0; i < axes.length; i++) {
1111                setRangeAxis(i, axes[i], false);   
1112            }
1113            notifyListeners(new PlotChangeEvent(this));
1114        }
1115        
1116        /**
1117         * Returns the number of range axes.
1118         *
1119         * @return The axis count.
1120         * 
1121         * @see #getDomainAxisCount()
1122         */
1123        public int getRangeAxisCount() {
1124            return this.rangeAxes.size();
1125        }
1126    
1127        /**
1128         * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
1129         * to all registered listeners.
1130         * 
1131         * @see #clearDomainAxes()
1132         */
1133        public void clearRangeAxes() {
1134            for (int i = 0; i < this.rangeAxes.size(); i++) {
1135                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1136                if (axis != null) {
1137                    axis.removeChangeListener(this);
1138                }
1139            }
1140            this.rangeAxes.clear();
1141            notifyListeners(new PlotChangeEvent(this));
1142        }
1143    
1144        /**
1145         * Configures the range axes.
1146         * 
1147         * @see #configureDomainAxes()
1148         */
1149        public void configureRangeAxes() {
1150            for (int i = 0; i < this.rangeAxes.size(); i++) {
1151                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1152                if (axis != null) {
1153                    axis.configure();
1154                }
1155            }
1156        }
1157    
1158        /**
1159         * Returns the location for a range axis.  If this hasn't been set
1160         * explicitly, the method returns the location that is opposite to the
1161         * primary range axis location.
1162         *
1163         * @param index  the axis index.
1164         *
1165         * @return The location (never <code>null</code>).
1166         * 
1167         * @see #setRangeAxisLocation(int, AxisLocation)
1168         */
1169        public AxisLocation getRangeAxisLocation(int index) {
1170            AxisLocation result = null;
1171            if (index < this.rangeAxisLocations.size()) {
1172                result = (AxisLocation) this.rangeAxisLocations.get(index);
1173            }
1174            if (result == null) {
1175                result = AxisLocation.getOpposite(getRangeAxisLocation());
1176            }
1177            return result;
1178        }
1179    
1180        /**
1181         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
1182         * to all registered listeners.
1183         *
1184         * @param index  the axis index.
1185         * @param location  the location (<code>null</code> permitted).
1186         * 
1187         * @see #getRangeAxisLocation(int)
1188         */
1189        public void setRangeAxisLocation(int index, AxisLocation location) {
1190            // delegate...
1191            setRangeAxisLocation(index, location, true);
1192        }
1193        
1194        /**
1195         * Sets the axis location for a domain axis and, if requested, sends a
1196         * {@link PlotChangeEvent} to all registered listeners.
1197         * 
1198         * @param index  the axis index.
1199         * @param location  the location (<code>null</code> not permitted for 
1200         *     index 0).
1201         * @param notify  notify listeners?
1202         * 
1203         * @since 1.0.5
1204         * 
1205         * @see #getRangeAxisLocation(int)
1206         * @see #setDomainAxisLocation(int, AxisLocation, boolean)
1207         */
1208        public void setRangeAxisLocation(int index, AxisLocation location, 
1209                boolean notify) {
1210            
1211            if (index == 0 && location == null) {
1212                throw new IllegalArgumentException(
1213                        "Null 'location' for index 0 not permitted.");
1214            }
1215            this.rangeAxisLocations.set(index, location);
1216            if (notify) {
1217                notifyListeners(new PlotChangeEvent(this));
1218            }   
1219        }
1220    
1221        /**
1222         * Returns the edge for a range axis.
1223         *
1224         * @param index  the axis index.
1225         *
1226         * @return The edge.
1227         * 
1228         * @see #getRangeAxisLocation(int)
1229         * @see #getOrientation()
1230         */
1231        public RectangleEdge getRangeAxisEdge(int index) {
1232            AxisLocation location = getRangeAxisLocation(index);
1233            RectangleEdge result = Plot.resolveRangeAxisLocation(location, 
1234                    this.orientation);
1235            if (result == null) {
1236                result = RectangleEdge.opposite(getRangeAxisEdge());
1237            }
1238            return result;
1239        }
1240    
1241        /**
1242         * Returns the primary dataset for the plot.
1243         *
1244         * @return The primary dataset (possibly <code>null</code>).
1245         * 
1246         * @see #getDataset(int)
1247         * @see #setDataset(XYDataset)
1248         */
1249        public XYDataset getDataset() {
1250            return getDataset(0);
1251        }
1252    
1253        /**
1254         * Returns a dataset.
1255         *
1256         * @param index  the dataset index.
1257         *
1258         * @return The dataset (possibly <code>null</code>).
1259         * 
1260         * @see #setDataset(int, XYDataset)
1261         */
1262        public XYDataset getDataset(int index) {
1263            XYDataset result = null;
1264            if (this.datasets.size() > index) {
1265                result = (XYDataset) this.datasets.get(index);
1266            }
1267            return result;
1268        }
1269    
1270        /**
1271         * Sets the primary dataset for the plot, replacing the existing dataset if
1272         * there is one.
1273         *
1274         * @param dataset  the dataset (<code>null</code> permitted).
1275         * 
1276         * @see #getDataset()
1277         * @see #setDataset(int, XYDataset)
1278         */
1279        public void setDataset(XYDataset dataset) {
1280            setDataset(0, dataset);
1281        }
1282    
1283        /**
1284         * Sets a dataset for the plot.
1285         *
1286         * @param index  the dataset index.
1287         * @param dataset  the dataset (<code>null</code> permitted).
1288         * 
1289         * @see #getDataset(int)
1290         */
1291        public void setDataset(int index, XYDataset dataset) {
1292            XYDataset existing = getDataset(index);
1293            if (existing != null) {
1294                existing.removeChangeListener(this);
1295            }
1296            this.datasets.set(index, dataset);
1297            if (dataset != null) {
1298                dataset.addChangeListener(this);
1299            }
1300    
1301            // send a dataset change event to self...
1302            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
1303            datasetChanged(event);
1304        }
1305    
1306        /**
1307         * Returns the number of datasets.
1308         *
1309         * @return The number of datasets.
1310         */
1311        public int getDatasetCount() {
1312            return this.datasets.size();
1313        }
1314    
1315        /**
1316         * Returns the index of the specified dataset, or <code>-1</code> if the
1317         * dataset does not belong to the plot.
1318         *
1319         * @param dataset  the dataset (<code>null</code> not permitted).
1320         *
1321         * @return The index.
1322         */
1323        public int indexOf(XYDataset dataset) {
1324            int result = -1;
1325            for (int i = 0; i < this.datasets.size(); i++) {
1326                if (dataset == this.datasets.get(i)) {
1327                    result = i;
1328                    break;
1329                }
1330            }
1331            return result;
1332        }
1333    
1334        /**
1335         * Maps a dataset to a particular domain axis.  All data will be plotted
1336         * against axis zero by default, no mapping is required for this case.
1337         *
1338         * @param index  the dataset index (zero-based).
1339         * @param axisIndex  the axis index.
1340         * 
1341         * @see #mapDatasetToRangeAxis(int, int)
1342         */
1343        public void mapDatasetToDomainAxis(int index, int axisIndex) {
1344            this.datasetToDomainAxisMap.put(new Integer(index), 
1345                    new Integer(axisIndex));
1346            // fake a dataset change event to update axes...
1347            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1348        }
1349    
1350        /**
1351         * Maps a dataset to a particular range axis.  All data will be plotted
1352         * against axis zero by default, no mapping is required for this case.
1353         *
1354         * @param index  the dataset index (zero-based).
1355         * @param axisIndex  the axis index.
1356         * 
1357         * @see #mapDatasetToDomainAxis(int, int)
1358         */
1359        public void mapDatasetToRangeAxis(int index, int axisIndex) {
1360            this.datasetToRangeAxisMap.put(new Integer(index), 
1361                    new Integer(axisIndex));
1362            // fake a dataset change event to update axes...
1363            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1364        }
1365    
1366        /**
1367         * Returns the renderer for the primary dataset.
1368         *
1369         * @return The item renderer (possibly <code>null</code>).
1370         * 
1371         * @see #setRenderer(XYItemRenderer)
1372         */
1373        public XYItemRenderer getRenderer() {
1374            return getRenderer(0);
1375        }
1376    
1377        /**
1378         * Returns the renderer for a dataset, or <code>null</code>.
1379         *
1380         * @param index  the renderer index.
1381         *
1382         * @return The renderer (possibly <code>null</code>).
1383         * 
1384         * @see #setRenderer(int, XYItemRenderer)
1385         */
1386        public XYItemRenderer getRenderer(int index) {
1387            XYItemRenderer result = null;
1388            if (this.renderers.size() > index) {
1389                result = (XYItemRenderer) this.renderers.get(index);
1390            }
1391            return result;
1392    
1393        }
1394    
1395        /**
1396         * Sets the renderer for the primary dataset and sends a
1397         * {@link PlotChangeEvent} to all registered listeners.  If the renderer
1398         * is set to <code>null</code>, no data will be displayed.
1399         *
1400         * @param renderer  the renderer (<code>null</code> permitted).
1401         * 
1402         * @see #getRenderer()
1403         */
1404        public void setRenderer(XYItemRenderer renderer) {
1405            setRenderer(0, renderer);
1406        }
1407    
1408        /**
1409         * Sets a renderer and sends a {@link PlotChangeEvent} to all
1410         * registered listeners.
1411         *
1412         * @param index  the index.
1413         * @param renderer  the renderer.