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