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        public XYItemRenderer getRenderer(int index) {
1414            XYItemRenderer result = null;
1415            if (this.renderers.size() > index) {
1416                result = (XYItemRenderer) this.renderers.get(index);
1417            }
1418            return result;
1419    
1420        }
1421    
1422        /**
1423         * Sets the renderer for the primary dataset and sends a
1424         * {@link PlotChangeEvent} to all registered listeners.  If the renderer
1425         * is set to <code>null</code>, no data will be displayed.
1426         *
1427         * @param renderer  the renderer (<code>null</code> permitted).
1428         *
1429         * @see #getRenderer()
1430         */
1431        public void setRenderer(XYItemRenderer renderer) {
1432            setRenderer(0, renderer);
1433        }
1434    
1435        /**
1436         * Sets a renderer and sends a {@link PlotChangeEvent} to all
1437         * registered listeners.
1438         *
1439         * @param index  the index.
1440         * @param renderer  the renderer.
1441         *
1442         * @see #getRenderer(int)
1443         */
1444        public void setRenderer(int index, XYItemRenderer renderer) {
1445            setRenderer(index, renderer, true);
1446        }
1447    
1448        /**
1449         * Sets a renderer and sends a {@link PlotChangeEvent} to all
1450         * registered listeners.
1451         *
1452         * @param index  the index.
1453         * @param renderer  the renderer.
1454         * @param notify  notify listeners?
1455         *
1456         * @see #getRenderer(int)
1457         */
1458        public void setRenderer(int index, XYItemRenderer renderer,
1459                                boolean notify) {
1460            XYItemRenderer existing = getRenderer(index);
1461            if (existing != null) {
1462                existing.removeChangeListener(this);
1463            }
1464            this.renderers.set(index, renderer);
1465            if (renderer != null) {
1466                renderer.setPlot(this);
1467                renderer.addChangeListener(this);
1468            }
1469            configureDomainAxes();
1470            configureRangeAxes();
1471            if (notify) {
1472                fireChangeEvent();
1473            }
1474        }
1475    
1476        /**
1477         * Sets the renderers for this plot and sends a {@link PlotChangeEvent}
1478         * to all registered listeners.
1479         *
1480         * @param renderers  the renderers (<code>null</code> not permitted).
1481         */
1482        public void setRenderers(XYItemRenderer[] renderers) {
1483            for (int i = 0; i < renderers.length; i++) {
1484                setRenderer(i, renderers[i], false);
1485            }
1486            fireChangeEvent();
1487        }
1488    
1489        /**
1490         * Returns the dataset rendering order.
1491         *
1492         * @return The order (never <code>null</code>).
1493         *
1494         * @see #setDatasetRenderingOrder(DatasetRenderingOrder)
1495         */
1496        public DatasetRenderingOrder getDatasetRenderingOrder() {
1497            return this.datasetRenderingOrder;
1498        }
1499    
1500        /**
1501         * Sets the rendering order and sends a {@link PlotChangeEvent} to all
1502         * registered listeners.  By default, the plot renders the primary dataset
1503         * last (so that the primary dataset overlays the secondary datasets).
1504         * You can reverse this if you want to.
1505         *
1506         * @param order  the rendering order (<code>null</code> not permitted).
1507         *
1508         * @see #getDatasetRenderingOrder()
1509         */
1510        public void setDatasetRenderingOrder(DatasetRenderingOrder order) {
1511            if (order == null) {
1512                throw new IllegalArgumentException("Null 'order' argument.");
1513            }
1514            this.datasetRenderingOrder = order;
1515            fireChangeEvent();
1516        }
1517    
1518        /**
1519         * Returns the series rendering order.
1520         *
1521         * @return the order (never <code>null</code>).
1522         *
1523         * @see #setSeriesRenderingOrder(SeriesRenderingOrder)
1524         */
1525        public SeriesRenderingOrder getSeriesRenderingOrder() {
1526            return this.seriesRenderingOrder;
1527        }
1528    
1529        /**
1530         * Sets the series order and sends a {@link PlotChangeEvent} to all
1531         * registered listeners.  By default, the plot renders the primary series
1532         * last (so that the primary series appears to be on top).
1533         * You can reverse this if you want to.
1534         *
1535         * @param order  the rendering order (<code>null</code> not permitted).
1536         *
1537         * @see #getSeriesRenderingOrder()
1538         */
1539        public void setSeriesRenderingOrder(SeriesRenderingOrder order) {
1540            if (order == null) {
1541                throw new IllegalArgumentException("Null 'order' argument.");
1542            }
1543            this.seriesRenderingOrder = order;
1544            fireChangeEvent();
1545        }
1546    
1547        /**
1548         * Returns the index of the specified renderer, or <code>-1</code> if the
1549         * renderer is not assigned to this plot.
1550         *
1551         * @param renderer  the renderer (<code>null</code> permitted).
1552         *
1553         * @return The renderer index.
1554         */
1555        public int getIndexOf(XYItemRenderer renderer) {
1556            return this.renderers.indexOf(renderer);
1557        }
1558    
1559        /**
1560         * Returns the renderer for the specified dataset.  The code first
1561         * determines the index of the dataset, then checks if there is a
1562         * renderer with the same index (if not, the method returns renderer(0).
1563         *
1564         * @param dataset  the dataset (<code>null</code> permitted).
1565         *
1566         * @return The renderer (possibly <code>null</code>).
1567         */
1568        public XYItemRenderer getRendererForDataset(XYDataset dataset) {
1569            XYItemRenderer result = null;
1570            for (int i = 0; i < this.datasets.size(); i++) {
1571                if (this.datasets.get(i) == dataset) {
1572                    result = (XYItemRenderer) this.renderers.get(i);
1573                    if (result == null) {
1574                        result = getRenderer();
1575                    }
1576                    break;
1577                }
1578            }
1579            return result;
1580        }
1581    
1582        /**
1583         * Returns the weight for this plot when it is used as a subplot within a
1584         * combined plot.
1585         *
1586         * @return The weight.
1587         *
1588         * @see #setWeight(int)
1589         */
1590        public int getWeight() {
1591            return this.weight;
1592        }
1593    
1594        /**
1595         * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all
1596         * registered listeners.
1597         *
1598         * @param weight  the weight.
1599         *
1600         * @see #getWeight()
1601         */
1602        public void setWeight(int weight) {
1603            this.weight = weight;
1604            fireChangeEvent();
1605        }
1606    
1607        /**
1608         * Returns <code>true</code> if the domain gridlines are visible, and
1609         * <code>false<code> otherwise.
1610         *
1611         * @return <code>true</code> or <code>false</code>.
1612         *
1613         * @see #setDomainGridlinesVisible(boolean)
1614         */
1615        public boolean isDomainGridlinesVisible() {
1616            return this.domainGridlinesVisible;
1617        }
1618    
1619        /**
1620         * Sets the flag that controls whether or not the domain grid-lines are
1621         * visible.
1622         * <p>
1623         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
1624         * registered listeners.
1625         *
1626         * @param visible  the new value of the flag.
1627         *
1628         * @see #isDomainGridlinesVisible()
1629         */
1630        public void setDomainGridlinesVisible(boolean visible) {
1631            if (this.domainGridlinesVisible != visible) {
1632                this.domainGridlinesVisible = visible;
1633                fireChangeEvent();
1634            }
1635        }
1636    
1637        /**
1638         * Returns the stroke for the grid-lines (if any) plotted against the
1639         * domain axis.
1640         *
1641         * @return The stroke (never <code>null</code>).
1642         *
1643         * @see #setDomainGridlineStroke(Stroke)
1644         */
1645        public Stroke getDomainGridlineStroke() {
1646            return this.domainGridlineStroke;
1647        }
1648    
1649        /**
1650         * Sets the stroke for the grid lines plotted against the domain axis, and
1651         * sends a {@link PlotChangeEvent} to all registered listeners.
1652         * <p>
1653         * If you set this to <code>null</code>, no grid lines will be drawn.
1654         *
1655         * @param stroke  the stroke (<code>null</code> not permitted).
1656         *
1657         * @throws IllegalArgumentException if <code>stroke</code> is
1658         *     <code>null</code>.
1659         *
1660         * @see #getDomainGridlineStroke()
1661         */
1662        public void setDomainGridlineStroke(Stroke stroke) {
1663            if (stroke == null) {
1664                throw new IllegalArgumentException("Null 'stroke' argument.");
1665            }
1666            this.domainGridlineStroke = stroke;
1667            fireChangeEvent();
1668        }
1669    
1670        /**
1671         * Returns the paint for the grid lines (if any) plotted against the domain
1672         * axis.
1673         *
1674         * @return The paint (never <code>null</code>).
1675         *
1676         * @see #setDomainGridlinePaint(Paint)
1677         */
1678        public Paint getDomainGridlinePaint() {
1679            return this.domainGridlinePaint;
1680        }
1681    
1682        /**
1683         * Sets the paint for the grid lines plotted against the domain axis, and
1684         * sends a {@link PlotChangeEvent} to all registered listeners.
1685         *
1686         * @param paint  the paint (<code>null</code> not permitted).
1687         *
1688         * @throws IllegalArgumentException if <code>paint</code> is
1689         *     <code>null</code>.
1690         *
1691         * @see #getDomainGridlinePaint()
1692         */
1693        public void setDomainGridlinePaint(Paint paint) {
1694            if (paint == null) {
1695                throw new IllegalArgumentException("Null 'paint' argument.");
1696            }
1697            this.domainGridlinePaint = paint;
1698            fireChangeEvent();
1699        }
1700    
1701        /**
1702         * Returns <code>true</code> if the range axis grid is visible, and
1703         * <code>false<code> otherwise.
1704         *
1705         * @return A boolean.
1706         *
1707         * @see #setRangeGridlinesVisible(boolean)
1708         */
1709        public boolean isRangeGridlinesVisible() {
1710            return this.rangeGridlinesVisible;
1711        }
1712    
1713        /**
1714         * Sets the flag that controls whether or not the range axis grid lines
1715         * are visible.
1716         * <p>
1717         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
1718         * registered listeners.
1719         *
1720         * @param visible  the new value of the flag.
1721         *
1722         * @see #isRangeGridlinesVisible()
1723         */
1724        public void setRangeGridlinesVisible(boolean visible) {
1725            if (this.rangeGridlinesVisible != visible) {
1726                this.rangeGridlinesVisible = visible;
1727                fireChangeEvent();
1728            }
1729        }
1730    
1731        /**
1732         * Returns the stroke for the grid lines (if any) plotted against the
1733         * range axis.
1734         *
1735         * @return The stroke (never <code>null</code>).
1736         *
1737         * @see #setRangeGridlineStroke(Stroke)
1738         */
1739        public Stroke getRangeGridlineStroke() {
1740            return this.rangeGridlineStroke;
1741        }
1742    
1743        /**
1744         * Sets the stroke for the grid lines plotted against the range axis,
1745         * and sends a {@link PlotChangeEvent} to all registered listeners.
1746         *
1747         * @param stroke  the stroke (<code>null</code> not permitted).
1748         *
1749         * @see #getRangeGridlineStroke()
1750         */
1751        public void setRangeGridlineStroke(Stroke stroke) {
1752            if (stroke == null) {
1753                throw new IllegalArgumentException("Null 'stroke' argument.");
1754            }
1755            this.rangeGridlineStroke = stroke;
1756            fireChangeEvent();
1757        }
1758    
1759        /**
1760         * Returns the paint for the grid lines (if any) plotted against the range
1761         * axis.
1762         *
1763         * @return The paint (never <code>null</code>).
1764         *
1765         * @see #setRangeGridlinePaint(Paint)
1766         */
1767        public Paint getRangeGridlinePaint() {
1768            return this.rangeGridlinePaint;
1769        }
1770    
1771        /**
1772         * Sets the paint for the grid lines plotted against the range axis and
1773         * sends a {@link PlotChangeEvent} to all registered listeners.
1774         *
1775         * @param paint  the paint (<code>null</code> not permitted).
1776         *
1777         * @see #getRangeGridlinePaint()
1778         */
1779        public void setRangeGridlinePaint(Paint paint) {
1780            if (paint == null) {
1781                throw new IllegalArgumentException("Null 'paint' argument.");
1782            }
1783            this.rangeGridlinePaint = paint;
1784            fireChangeEvent();
1785        }
1786    
1787        /**
1788         * Returns a flag that controls whether or not a zero baseline is
1789         * displayed for the domain axis.
1790         *
1791         * @return A boolean.
1792         *
1793         * @since 1.0.5
1794         *
1795         * @see #setDomainZeroBaselineVisible(boolean)
1796         */
1797        public boolean isDomainZeroBaselineVisible() {
1798            return this.domainZeroBaselineVisible;
1799        }
1800    
1801        /**
1802         * Sets the flag that controls whether or not the zero baseline is
1803         * displayed for the domain axis, and sends a {@link PlotChangeEvent} to
1804         * all registered listeners.
1805         *
1806         * @param visible  the flag.
1807         *
1808         * @since 1.0.5
1809         *
1810         * @see #isDomainZeroBaselineVisible()
1811         */
1812        public void setDomainZeroBaselineVisible(boolean visible) {
1813            this.domainZeroBaselineVisible = visible;
1814            fireChangeEvent();
1815        }
1816    
1817        /**
1818         * Returns the stroke used for the zero baseline against the domain axis.
1819         *
1820         * @return The stroke (never <code>null</code>).
1821         *
1822         * @since 1.0.5
1823         *
1824         * @see #setDomainZeroBaselineStroke(Stroke)
1825         */
1826        public Stroke getDomainZeroBaselineStroke() {
1827            return this.domainZeroBaselineStroke;
1828        }
1829    
1830        /**
1831         * Sets the stroke for the zero baseline for the domain axis,
1832         * and sends a {@link PlotChangeEvent} to all registered listeners.
1833         *
1834         * @param stroke  the stroke (<code>null</code> not permitted).
1835         *
1836         * @since 1.0.5
1837         *
1838         * @see #getRangeZeroBaselineStroke()
1839         */
1840        public void setDomainZeroBaselineStroke(Stroke stroke) {
1841            if (stroke == null) {
1842                throw new IllegalArgumentException("Null 'stroke' argument.");
1843            }
1844            this.domainZeroBaselineStroke = stroke;
1845            fireChangeEvent();
1846        }
1847    
1848        /**
1849         * Returns the paint for the zero baseline (if any) plotted against the
1850         * domain axis.
1851         *
1852         * @since 1.0.5
1853         *
1854         * @return The paint (never <code>null</code>).
1855         *
1856         * @see #setDomainZeroBaselinePaint(Paint)
1857         */
1858        public Paint getDomainZeroBaselinePaint() {
1859            return this.domainZeroBaselinePaint;
1860        }
1861    
1862        /**
1863         * Sets the paint for the zero baseline plotted against the domain axis and
1864         * sends a {@link PlotChangeEvent} to all registered listeners.
1865         *
1866         * @param paint  the paint (<code>null</code> not permitted).
1867         *
1868         * @since 1.0.5
1869         *
1870         * @see #getDomainZeroBaselinePaint()
1871         */
1872        public void setDomainZeroBaselinePaint(Paint paint) {
1873            if (paint == null) {
1874                throw new IllegalArgumentException("Null 'paint' argument.");
1875            }
1876            this.domainZeroBaselinePaint = paint;
1877            fireChangeEvent();
1878        }
1879    
1880        /**
1881         * Returns a flag that controls whether or not a zero baseline is
1882         * displayed for the range axis.
1883         *
1884         * @return A boolean.
1885         *
1886         * @see #setRangeZeroBaselineVisible(boolean)
1887         */
1888        public boolean isRangeZeroBaselineVisible() {
1889            return this.rangeZeroBaselineVisible;
1890        }
1891    
1892        /**
1893         * Sets the flag that controls whether or not the zero baseline is
1894         * displayed for the range axis, and sends a {@link PlotChangeEvent} to
1895         * all registered listeners.
1896         *
1897         * @param visible  the flag.
1898         *
1899         * @see #isRangeZeroBaselineVisible()
1900         */
1901        public void setRangeZeroBaselineVisible(boolean visible) {
1902            this.rangeZeroBaselineVisible = visible;
1903            fireChangeEvent();
1904        }
1905    
1906        /**
1907         * Returns the stroke used for the zero baseline against the range axis.
1908         *
1909         * @return The stroke (never <code>null</code>).
1910         *
1911         * @see #setRangeZeroBaselineStroke(Stroke)
1912         */
1913        public Stroke getRangeZeroBaselineStroke() {
1914            return this.rangeZeroBaselineStroke;
1915        }
1916    
1917        /**
1918         * Sets the stroke for the zero baseline for the range axis,
1919         * and sends a {@link PlotChangeEvent} to all registered listeners.
1920         *
1921         * @param stroke  the stroke (<code>null</code> not permitted).
1922         *
1923         * @see #getRangeZeroBaselineStroke()
1924         */
1925        public void setRangeZeroBaselineStroke(Stroke stroke) {
1926            if (stroke == null) {
1927                throw new IllegalArgumentException("Null 'stroke' argument.");
1928            }
1929            this.rangeZeroBaselineStroke = stroke;
1930            fireChangeEvent();
1931        }
1932    
1933        /**
1934         * Returns the paint for the zero baseline (if any) plotted against the
1935         * range axis.
1936         *
1937         * @return The paint (never <code>null</code>).
1938         *
1939         * @see #setRangeZeroBaselinePaint(Paint)
1940         */
1941        public Paint getRangeZeroBaselinePaint() {
1942            return this.rangeZeroBaselinePaint;
1943        }
1944    
1945        /**
1946         * Sets the paint for the zero baseline plotted against the range axis and
1947         * sends a {@link PlotChangeEvent} to all registered listeners.
1948         *
1949         * @param paint  the paint (<code>null</code> not permitted).
1950         *
1951         * @see #getRangeZeroBaselinePaint()
1952         */
1953        public void setRangeZeroBaselinePaint(Paint paint) {
1954            if (paint == null) {
1955                throw new IllegalArgumentException("Null 'paint' argument.");
1956            }
1957            this.rangeZeroBaselinePaint = paint;
1958            fireChangeEvent();
1959        }
1960    
1961        /**
1962         * Returns the paint used for the domain tick bands.  If this is
1963         * <code>null</code>, no tick bands will be drawn.
1964         *
1965         * @return The paint (possibly <code>null</code>).
1966         *
1967         * @see #setDomainTickBandPaint(Paint)
1968         */
1969        public Paint getDomainTickBandPaint() {
1970            return this.domainTickBandPaint;
1971        }
1972    
1973        /**
1974         * Sets the paint for the domain tick bands.
1975         *
1976         * @param paint  the paint (<code>null</code> permitted).
1977         *
1978         * @see #getDomainTickBandPaint()
1979         */
1980        public void setDomainTickBandPaint(Paint paint) {
1981            this.domainTickBandPaint = paint;
1982            fireChangeEvent();
1983        }
1984    
1985        /**
1986         * Returns the paint used for the range tick bands.  If this is
1987         * <code>null</code>, no tick bands will be drawn.
1988         *
1989         * @return The paint (possibly <code>null</code>).
1990         *
1991         * @see #setRangeTickBandPaint(Paint)
1992         */
1993        public Paint getRangeTickBandPaint() {
1994            return this.rangeTickBandPaint;
1995        }
1996    
1997        /**
1998         * Sets the paint for the range tick bands.
1999         *
2000         * @param paint  the paint (<code>null</code> permitted).
2001         *
2002         * @see #getRangeTickBandPaint()
2003         */
2004        public void setRangeTickBandPaint(Paint paint) {
2005            this.rangeTickBandPaint = paint;
2006            fireChangeEvent();
2007        }
2008    
2009        /**
2010         * Returns the origin for the quadrants that can be displayed on the plot.
2011         * This defaults to (0, 0).
2012         *
2013         * @return The origin point (never <code>null</code>).
2014         *
2015         * @see #setQuadrantOrigin(Point2D)
2016         */
2017        public Point2D getQuadrantOrigin() {
2018            return this.quadrantOrigin;
2019        }
2020    
2021        /**
2022         * Sets the quadrant origin and sends a {@link PlotChangeEvent} to all
2023         * registered listeners.
2024         *
2025         * @param origin  the origin (<code>null</code> not permitted).
2026         *
2027         * @see #getQuadrantOrigin()
2028         */
2029        public void setQuadrantOrigin(Point2D origin) {
2030            if (origin == null) {
2031                throw new IllegalArgumentException("Null 'origin' argument.");
2032            }
2033            this.quadrantOrigin = origin;
2034            fireChangeEvent();
2035        }
2036    
2037        /**
2038         * Returns the paint used for the specified quadrant.
2039         *
2040         * @param index  the quadrant index (0-3).
2041         *
2042         * @return The paint (possibly <code>null</code>).
2043         *
2044         * @see #setQuadrantPaint(int, Paint)
2045         */
2046        public Paint getQuadrantPaint(int index) {
2047            if (index < 0 || index > 3) {
2048                throw new IllegalArgumentException("The index value (" + index
2049                        + ") should be in the range 0 to 3.");
2050            }
2051            return this.quadrantPaint[index];
2052        }
2053    
2054        /**
2055         * Sets the paint used for the specified quadrant and sends a
2056         * {@link PlotChangeEvent} to all registered listeners.
2057         *
2058         * @param index  the quadrant index (0-3).
2059         * @param paint  the paint (<code>null</code> permitted).
2060         *
2061         * @see #getQuadrantPaint(int)
2062         */
2063        public void setQuadrantPaint(int index, Paint paint) {
2064            if (index < 0 || index > 3) {
2065                throw new IllegalArgumentException("The index value (" + index
2066                        + ") should be in the range 0 to 3.");
2067            }
2068            this.quadrantPaint[index] = paint;
2069            fireChangeEvent();
2070        }
2071    
2072        /**
2073         * Adds a marker for the domain axis and sends a {@link PlotChangeEvent}
2074         * to all registered listeners.
2075         * <P>
2076         * Typically a marker will be drawn by the renderer as a line perpendicular
2077         * to the range axis, however this is entirely up to the renderer.
2078         *
2079         * @param marker  the marker (<code>null</code> not permitted).
2080         *
2081         * @see #addDomainMarker(Marker, Layer)
2082         * @see #clearDomainMarkers()
2083         */
2084        public void addDomainMarker(Marker marker) {
2085            // defer argument checking...
2086            addDomainMarker(marker, Layer.FOREGROUND);
2087        }
2088    
2089        /**
2090         * Adds a marker for the domain axis in the specified layer and sends a
2091         * {@link PlotChangeEvent} to all registered listeners.
2092         * <P>
2093         * Typically a marker will be drawn by the renderer as a line perpendicular
2094         * to the range axis, however this is entirely up to the renderer.
2095         *
2096         * @param marker  the marker (<code>null</code> not permitted).
2097         * @param layer  the layer (foreground or background).
2098         *
2099         * @see #addDomainMarker(int, Marker, Layer)
2100         */
2101        public void addDomainMarker(Marker marker, Layer layer) {
2102            addDomainMarker(0, marker, layer);
2103        }
2104    
2105        /**
2106         * Clears all the (foreground and background) domain markers and sends a
2107         * {@link PlotChangeEvent} to all registered listeners.
2108         *
2109         * @see #addDomainMarker(int, Marker, Layer)
2110         */
2111        public void clearDomainMarkers() {
2112            if (this.backgroundDomainMarkers != null) {
2113                Set keys = this.backgroundDomainMarkers.keySet();
2114                Iterator iterator = keys.iterator();
2115                while (iterator.hasNext()) {
2116                    Integer key = (Integer) iterator.next();
2117                    clearDomainMarkers(key.intValue());
2118                }
2119                this.backgroundDomainMarkers.clear();
2120            }
2121            if (this.foregroundDomainMarkers != null) {
2122                Set keys = this.foregroundDomainMarkers.keySet();
2123                Iterator iterator = keys.iterator();
2124                while (iterator.hasNext()) {
2125                    Integer key = (Integer) iterator.next();
2126                    clearDomainMarkers(key.intValue());
2127                }
2128                this.foregroundDomainMarkers.clear();
2129            }
2130            fireChangeEvent();
2131        }
2132    
2133        /**
2134         * Clears the (foreground and background) domain markers for a particular
2135         * renderer.
2136         *
2137         * @param index  the renderer index.
2138         *
2139         * @see #clearRangeMarkers(int)
2140         */
2141        public void clearDomainMarkers(int index) {
2142            Integer key = new Integer(index);
2143            if (this.backgroundDomainMarkers != null) {
2144                Collection markers
2145                    = (Collection) this.backgroundDomainMarkers.get(key);
2146                if (markers != null) {
2147                    Iterator iterator = markers.iterator();
2148                    while (iterator.hasNext()) {
2149                        Marker m = (Marker) iterator.next();
2150                        m.removeChangeListener(this);
2151                    }
2152                    markers.clear();
2153                }
2154            }
2155            if (this.foregroundRangeMarkers != null) {
2156                Collection markers
2157                    = (Collection) this.foregroundDomainMarkers.get(key);
2158                if (markers != null) {
2159                    Iterator iterator = markers.iterator();
2160                    while (iterator.hasNext()) {
2161                        Marker m = (Marker) iterator.next();
2162                        m.removeChangeListener(this);
2163                    }
2164                    markers.clear();
2165                }
2166            }
2167            fireChangeEvent();
2168        }
2169    
2170        /**
2171         * Adds a marker for a specific dataset/renderer and sends a
2172         * {@link PlotChangeEvent} to all registered listeners.
2173         * <P>
2174         * Typically a marker will be drawn by the renderer as a line perpendicular
2175         * to the domain axis (that the renderer is mapped to), however this is
2176         * entirely up to the renderer.
2177         *
2178         * @param index  the dataset/renderer index.
2179         * @param marker  the marker.
2180         * @param layer  the layer (foreground or background).
2181         *
2182         * @see #clearDomainMarkers(int)
2183         * @see #addRangeMarker(int, Marker, Layer)
2184         */
2185        public void addDomainMarker(int index, Marker marker, Layer layer) {
2186            addDomainMarker(index, marker, layer, true);
2187        }
2188    
2189        /**
2190         * Adds a marker for a specific dataset/renderer and, if requested, sends a
2191         * {@link PlotChangeEvent} to all registered listeners.
2192         * <P>
2193         * Typically a marker will be drawn by the renderer as a line perpendicular
2194         * to the domain axis (that the renderer is mapped to), however this is
2195         * entirely up to the renderer.
2196         *
2197         * @param index  the dataset/renderer index.
2198         * @param marker  the marker.
2199         * @param layer  the layer (foreground or background).
2200         * @param notify  notify listeners?
2201         *
2202         * @since 1.0.10
2203         */
2204        public void addDomainMarker(int index, Marker marker, Layer layer,
2205                boolean notify) {
2206            if (marker == null) {
2207                throw new IllegalArgumentException("Null 'marker' not permitted.");
2208            }
2209            if (layer == null) {
2210                throw new IllegalArgumentException("Null 'layer' not permitted.");
2211            }
2212            Collection markers;
2213            if (layer == Layer.FOREGROUND) {
2214                markers = (Collection) this.foregroundDomainMarkers.get(
2215                        new Integer(index));
2216                if (markers == null) {
2217                    markers = new java.util.ArrayList();
2218                    this.foregroundDomainMarkers.put(new Integer(index), markers);
2219                }
2220                markers.add(marker);
2221            }
2222            else if (layer == Layer.BACKGROUND) {
2223                markers = (Collection) this.backgroundDomainMarkers.get(
2224                        new Integer(index));
2225                if (markers == null) {
2226                    markers = new java.util.ArrayList();
2227                    this.backgroundDomainMarkers.put(new Integer(index), markers);
2228                }
2229                markers.add(marker);
2230            }
2231            marker.addChangeListener(this);
2232            if (notify) {
2233                fireChangeEvent();
2234            }
2235        }
2236    
2237        /**
2238         * Removes a marker for the domain axis and sends a {@link PlotChangeEvent}
2239         * to all registered listeners.
2240         *
2241         * @param marker  the marker.
2242         *
2243         * @return A boolean indicating whether or not the marker was actually
2244         *         removed.
2245         *
2246         * @since 1.0.7
2247         */
2248        public boolean removeDomainMarker(Marker marker) {
2249            return removeDomainMarker(marker, Layer.FOREGROUND);
2250        }
2251    
2252        /**
2253         * Removes a marker for the domain axis in the specified layer and sends a
2254         * {@link PlotChangeEvent} to all registered listeners.
2255         *
2256         * @param marker the marker (<code>null</code> not permitted).
2257         * @param layer the layer (foreground or background).
2258         *
2259         * @return A boolean indicating whether or not the marker was actually
2260         *         removed.
2261         *
2262         * @since 1.0.7
2263         */
2264        public boolean removeDomainMarker(Marker marker, Layer layer) {
2265            return removeDomainMarker(0, marker, layer);
2266        }
2267    
2268        /**
2269         * Removes a marker for a specific dataset/renderer and sends a
2270         * {@link PlotChangeEvent} to all registered listeners.
2271         *
2272         * @param index the dataset/renderer index.
2273         * @param marker the marker.
2274         * @param layer the layer (foreground or background).
2275         *
2276         * @return A boolean indicating whether or not the marker was actually
2277         *         removed.
2278         *
2279         * @since 1.0.7
2280         */
2281        public boolean removeDomainMarker(int index, Marker marker, Layer layer) {
2282            return removeDomainMarker(index, marker, layer, true);
2283        }
2284    
2285        /**
2286         * Removes a marker for a specific dataset/renderer and, if requested,
2287         * sends a {@link PlotChangeEvent} to all registered listeners.
2288         *
2289         * @param index  the dataset/renderer index.
2290         * @param marker  the marker.
2291         * @param layer  the layer (foreground or background).
2292         * @param notify  notify listeners?
2293         *
2294         * @return A boolean indicating whether or not the marker was actually
2295         *         removed.
2296         *
2297         * @since 1.0.10
2298         */
2299        public boolean removeDomainMarker(int index, Marker marker, Layer layer,
2300                boolean notify) {
2301            ArrayList markers;
2302            if (layer == Layer.FOREGROUND) {
2303                markers = (ArrayList) this.foregroundDomainMarkers.get(
2304                        new Integer(index));
2305            }
2306            else {
2307                markers = (ArrayList) this.backgroundDomainMarkers.get(
2308                        new Integer(index));
2309            }
2310            if (markers == null) {
2311                return false;
2312            }
2313            boolean removed = markers.remove(marker);
2314            if (removed && notify) {
2315                fireChangeEvent();
2316            }
2317            return removed;
2318        }
2319    
2320        /**
2321         * Adds a marker for the range axis and sends a {@link PlotChangeEvent} to
2322         * all registered listeners.
2323         * <P>
2324         * Typically a marker will be drawn by the renderer as a line perpendicular
2325         * to the range axis, however this is entirely up to the renderer.
2326         *
2327         * @param marker  the marker (<code>null</code> not permitted).
2328         *
2329         * @see #addRangeMarker(Marker, Layer)
2330         */
2331        public void addRangeMarker(Marker marker) {
2332            addRangeMarker(marker, Layer.FOREGROUND);
2333        }
2334    
2335        /**
2336         * Adds a marker for the range axis in the specified layer and sends a
2337         * {@link PlotChangeEvent} to all registered listeners.
2338         * <P>
2339         * Typically a marker will be drawn by the renderer as a line perpendicular
2340         * to the range axis, however this is entirely up to the renderer.
2341         *
2342         * @param marker  the marker (<code>null</code> not permitted).
2343         * @param layer  the layer (foreground or background).
2344         *
2345         * @see #addRangeMarker(int, Marker, Layer)
2346         */
2347        public void addRangeMarker(Marker marker, Layer layer) {
2348            addRangeMarker(0, marker, layer);
2349        }
2350    
2351        /**
2352         * Clears all the range markers and sends a {@link PlotChangeEvent} to all
2353         * registered listeners.
2354         *
2355         * @see #clearRangeMarkers()
2356         */
2357        public void clearRangeMarkers() {
2358            if (this.backgroundRangeMarkers != null) {
2359                Set keys = this.backgroundRangeMarkers.keySet();
2360                Iterator iterator = keys.iterator();
2361                while (iterator.hasNext()) {
2362                    Integer key = (Integer) iterator.next();
2363                    clearRangeMarkers(key.intValue());
2364                }
2365                this.backgroundRangeMarkers.clear();
2366            }
2367            if (this.foregroundRangeMarkers != null) {
2368                Set keys = this.foregroundRangeMarkers.keySet();
2369                Iterator iterator = keys.iterator();
2370                while (iterator.hasNext()) {
2371                    Integer key = (Integer) iterator.next();
2372                    clearRangeMarkers(key.intValue());
2373                }
2374                this.foregroundRangeMarkers.clear();
2375            }
2376            fireChangeEvent();
2377        }
2378    
2379        /**
2380         * Adds a marker for a specific dataset/renderer and sends a
2381         * {@link PlotChangeEvent} to all registered listeners.
2382         * <P>
2383         * Typically a marker will be drawn by the renderer as a line perpendicular
2384         * to the range axis, however this is entirely up to the renderer.
2385         *
2386         * @param index  the dataset/renderer index.
2387         * @param marker  the marker.
2388         * @param layer  the layer (foreground or background).
2389         *
2390         * @see #clearRangeMarkers(int)
2391         * @see #addDomainMarker(int, Marker, Layer)
2392         */
2393        public void addRangeMarker(int index, Marker marker, Layer layer) {
2394            addRangeMarker(index, marker, layer, true);
2395        }
2396    
2397        /**
2398         * Adds a marker for a specific dataset/renderer and, if requested, sends a
2399         * {@link PlotChangeEvent} to all registered listeners.
2400         * <P>
2401         * Typically a marker will be drawn by the renderer as a line perpendicular
2402         * to the range axis, however this is entirely up to the renderer.
2403         *
2404         * @param index  the dataset/renderer index.
2405         * @param marker  the marker.
2406         * @param layer  the layer (foreground or background).
2407         * @param notify  notify listeners?
2408         *
2409         * @since 1.0.10
2410         */
2411        public void addRangeMarker(int index, Marker marker, Layer layer,
2412                boolean notify) {
2413            Collection markers;
2414            if (layer == Layer.FOREGROUND) {
2415                markers = (Collection) this.foregroundRangeMarkers.get(
2416                        new Integer(index));
2417                if (markers == null) {
2418                    markers = new java.util.ArrayList();
2419                    this.foregroundRangeMarkers.put(new Integer(index), markers);
2420                }
2421                markers.add(marker);
2422            }
2423            else if (layer == Layer.BACKGROUND) {
2424                markers = (Collection) this.backgroundRangeMarkers.get(
2425                        new Integer(index));
2426                if (markers == null) {
2427                    markers = new java.util.ArrayList();
2428                    this.backgroundRangeMarkers.put(new Integer(index), markers);
2429                }
2430                markers.add(marker);
2431            }
2432            marker.addChangeListener(this);
2433            if (notify) {
2434                fireChangeEvent();
2435            }
2436        }
2437    
2438        /**
2439         * Clears the (foreground and background) range markers for a particular
2440         * renderer.
2441         *
2442         * @param index  the renderer index.
2443         */
2444        public void clearRangeMarkers(int index) {
2445            Integer key = new Integer(index);
2446            if (this.backgroundRangeMarkers != null) {
2447                Collection markers
2448                    = (Collection) this.backgroundRangeMarkers.get(key);
2449                if (markers != null) {
2450                    Iterator iterator = markers.iterator();
2451                    while (iterator.hasNext()) {
2452                        Marker m = (Marker) iterator.next();
2453                        m.removeChangeListener(this);
2454                    }
2455                    markers.clear();
2456                }
2457            }
2458            if (this.foregroundRangeMarkers != null) {
2459                Collection markers
2460                    = (Collection) this.foregroundRangeMarkers.get(key);
2461                if (markers != null) {
2462                    Iterator iterator = markers.iterator();
2463                    while (iterator.hasNext()) {
2464                        Marker m = (Marker) iterator.next();
2465                        m.removeChangeListener(this);
2466                    }
2467                    markers.clear();
2468                }
2469            }
2470            fireChangeEvent();
2471        }
2472    
2473        /**
2474         * Removes a marker for the range axis and sends a {@link PlotChangeEvent}
2475         * to all registered listeners.
2476         *
2477         * @param marker the marker.
2478         *
2479         * @return A boolean indicating whether or not the marker was actually
2480         *         removed.
2481         *
2482         * @since 1.0.7
2483         */
2484        public boolean removeRangeMarker(Marker marker) {
2485            return removeRangeMarker(marker, Layer.FOREGROUND);
2486        }
2487    
2488        /**
2489         * Removes a marker for the range axis in the specified layer and sends a
2490         * {@link PlotChangeEvent} to all registered listeners.
2491         *
2492         * @param marker the marker (<code>null</code> not permitted).
2493         * @param layer the layer (foreground or background).
2494         *
2495         * @return A boolean indicating whether or not the marker was actually
2496         *         removed.
2497         *
2498         * @since 1.0.7
2499         */
2500        public boolean removeRangeMarker(Marker marker, Layer layer) {
2501            return removeRangeMarker(0, marker, layer);
2502        }
2503    
2504        /**
2505         * Removes a marker for a specific dataset/renderer and sends a
2506         * {@link PlotChangeEvent} to all registered listeners.
2507         *
2508         * @param index the dataset/renderer index.
2509         * @param marker the marker.
2510         * @param layer the layer (foreground or background).
2511         *
2512         * @return A boolean indicating whether or not the marker was actually
2513         *         removed.
2514         *
2515         * @since 1.0.7
2516         */
2517        public boolean removeRangeMarker(int index, Marker marker, Layer layer) {
2518            return removeRangeMarker(index, marker, layer, true);
2519        }
2520    
2521        /**
2522         * Removes a marker for a specific dataset/renderer and sends a
2523         * {@link PlotChangeEvent} to all registered listeners.
2524         *
2525         * @param index  the dataset/renderer index.
2526         * @param marker  the marker.
2527         * @param layer  the layer (foreground or background).
2528         * @param notify  notify listeners?
2529         *
2530         * @return A boolean indicating whether or not the marker was actually
2531         *         removed.
2532         *
2533         * @since 1.0.10
2534         */
2535        public boolean removeRangeMarker(int index, Marker marker, Layer layer,
2536                boolean notify) {
2537            if (marker == null) {
2538                throw new IllegalArgumentException("Null 'marker' argument.");
2539            }
2540            ArrayList markers;
2541            if (layer == Layer.FOREGROUND) {
2542                markers = (ArrayList) this.foregroundRangeMarkers.get(
2543                        new Integer(index));
2544            }
2545            else {
2546                markers = (ArrayList) this.backgroundRangeMarkers.get(
2547                        new Integer(index));
2548            }
2549            if (markers == null) {
2550                return false;
2551            }
2552            boolean removed = markers.remove(marker);
2553            if (removed && notify) {
2554                fireChangeEvent();
2555            }
2556            return removed;
2557        }
2558    
2559        /**
2560         * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to
2561         * all registered listeners.
2562         *
2563         * @param annotation  the annotation (<code>null</code> not permitted).
2564         *
2565         * @see #getAnnotations()
2566         * @see #removeAnnotation(XYAnnotation)
2567         */
2568        public void addAnnotation(XYAnnotation annotation) {
2569            addAnnotation(annotation, true);
2570        }
2571    
2572        /**
2573         * Adds an annotation to the plot and, if requested, sends a
2574         * {@link PlotChangeEvent} to all registered listeners.
2575         *
2576         * @param annotation  the annotation (<code>null</code> not permitted).
2577         * @param notify  notify listeners?
2578         *
2579         * @since 1.0.10
2580         */
2581        public void addAnnotation(XYAnnotation annotation, boolean notify) {
2582            if (annotation == null) {
2583                throw new IllegalArgumentException("Null 'annotation' argument.");
2584            }
2585            this.annotations.add(annotation);
2586            if (notify) {
2587                fireChangeEvent();
2588            }
2589        }
2590    
2591        /**
2592         * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
2593         * to all registered listeners.
2594         *
2595         * @param annotation  the annotation (<code>null</code> not permitted).
2596         *
2597         * @return A boolean (indicates whether or not the annotation was removed).
2598         *
2599         * @see #addAnnotation(XYAnnotation)
2600         * @see #getAnnotations()
2601         */
2602        public boolean removeAnnotation(XYAnnotation annotation) {
2603            return removeAnnotation(annotation, true);
2604        }
2605    
2606        /**
2607         * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
2608         * to all registered listeners.
2609         *
2610         * @param annotation  the annotation (<code>null</code> not permitted).
2611         * @param notify  notify listeners?
2612         *
2613         * @return A boolean (indicates whether or not the annotation was removed).
2614         *
2615         * @since 1.0.10
2616         */
2617        public boolean removeAnnotation(XYAnnotation annotation, boolean notify) {
2618            if (annotation == null) {
2619                throw new IllegalArgumentException("Null 'annotation' argument.");
2620            }
2621            boolean removed = this.annotations.remove(annotation);
2622            if (removed && notify) {
2623                fireChangeEvent();
2624            }
2625            return removed;
2626        }
2627    
2628        /**
2629         * Returns the list of annotations.
2630         *
2631         * @return The list of annotations.
2632         *
2633         * @since 1.0.1
2634         *
2635         * @see #addAnnotation(XYAnnotation)
2636         */
2637        public List getAnnotations() {
2638            return new ArrayList(this.annotations);
2639        }
2640    
2641        /**
2642         * Clears all the annotations and sends a {@link PlotChangeEvent} to all
2643         * registered listeners.
2644         *
2645         * @see #addAnnotation(XYAnnotation)
2646         */
2647        public void clearAnnotations() {
2648            this.annotations.clear();
2649            fireChangeEvent();
2650        }
2651    
2652        /**
2653         * Calculates the space required for all the axes in the plot.
2654         *
2655         * @param g2  the graphics device.
2656         * @param plotArea  the plot area.
2657         *
2658         * @return The required space.
2659         */
2660        protected AxisSpace calculateAxisSpace(Graphics2D g2,
2661                                               Rectangle2D plotArea) {
2662            AxisSpace space = new AxisSpace();
2663            space = calculateRangeAxisSpace(g2, plotArea, space);
2664            Rectangle2D revPlotArea = space.shrink(plotArea, null);
2665            space = calculateDomainAxisSpace(g2, revPlotArea, space);
2666            return space;
2667        }
2668    
2669        /**
2670         * Calculates the space required for the domain axis/axes.
2671         *
2672         * @param g2  the graphics device.
2673         * @param plotArea  the plot area.
2674         * @param space  a carrier for the result (<code>null</code> permitted).
2675         *
2676         * @return The required space.
2677         */
2678        protected AxisSpace calculateDomainAxisSpace(Graphics2D g2,
2679                                                     Rectangle2D plotArea,
2680                                                     AxisSpace space) {
2681    
2682            if (space == null) {
2683                space = new AxisSpace();
2684            }
2685    
2686            // reserve some space for the domain axis...
2687            if (this.fixedDomainAxisSpace != null) {
2688                if (this.orientation == PlotOrientation.HORIZONTAL) {
2689                    space.ensureAtLeast(this.fixedDomainAxisSpace.getLeft(),
2690                            RectangleEdge.LEFT);
2691                    space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(),
2692                            RectangleEdge.RIGHT);
2693                }
2694                else if (this.orientation == PlotOrientation.VERTICAL) {
2695                    space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(),
2696                            RectangleEdge.TOP);
2697                    space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(),
2698                            RectangleEdge.BOTTOM);
2699                }
2700            }
2701            else {
2702                // reserve space for the domain axes...
2703                for (int i = 0; i < this.domainAxes.size(); i++) {
2704                    Axis axis = (Axis) this.domainAxes.get(i);
2705                    if (axis != null) {
2706                        RectangleEdge edge = getDomainAxisEdge(i);
2707                        space = axis.reserveSpace(g2, this, plotArea, edge, space);
2708                    }
2709                }
2710            }
2711    
2712            return space;
2713    
2714        }
2715    
2716        /**
2717         * Calculates the space required for the range axis/axes.
2718         *
2719         * @param g2  the graphics device.
2720         * @param plotArea  the plot area.
2721         * @param space  a carrier for the result (<code>null</code> permitted).
2722         *
2723         * @return The required space.
2724         */
2725        protected AxisSpace calculateRangeAxisSpace(Graphics2D g2,
2726                                                    Rectangle2D plotArea,
2727                                                    AxisSpace space) {
2728    
2729            if (space == null) {
2730                space = new AxisSpace();
2731            }
2732    
2733            // reserve some space for the range axis...
2734            if (this.fixedRangeAxisSpace != null) {
2735                if (this.orientation == PlotOrientation.HORIZONTAL) {
2736                    space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(),
2737                            RectangleEdge.TOP);
2738                    space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(),
2739                            RectangleEdge.BOTTOM);
2740                }
2741                else if (this.orientation == PlotOrientation.VERTICAL) {
2742                    space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(),
2743                            RectangleEdge.LEFT);
2744                    space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(),
2745                            RectangleEdge.RIGHT);
2746                }
2747            }
2748            else {
2749                // reserve space for the range axes...
2750                for (int i = 0; i < this.rangeAxes.size(); i++) {
2751                    Axis axis = (Axis) this.rangeAxes.get(i);
2752                    if (axis != null) {
2753                        RectangleEdge edge = getRangeAxisEdge(i);
2754                        space = axis.reserveSpace(g2, this, plotArea, edge, space);
2755                    }
2756                }
2757            }
2758            return space;
2759    
2760        }
2761    
2762        /**
2763         * Draws the plot within the specified area on a graphics device.
2764         *
2765         * @param g2  the graphics device.
2766         * @param area  the plot area (in Java2D space).
2767         * @param anchor  an anchor point in Java2D space (<code>null</code>
2768         *                permitted).
2769         * @param parentState  the state from the parent plot, if there is one
2770         *                     (<code>null</code> permitted).
2771         * @param info  collects chart drawing information (<code>null</code>
2772         *              permitted).
2773         */
2774        public void draw(Graphics2D g2,
2775                         Rectangle2D area,
2776                         Point2D anchor,
2777                         PlotState parentState,
2778                         PlotRenderingInfo info) {
2779    
2780            // if the plot area is too small, just return...
2781            boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
2782            boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
2783            if (b1 || b2) {
2784                return;
2785            }
2786    
2787            // record the plot area...
2788            if (info != null) {
2789                info.setPlotArea(area);
2790            }
2791    
2792            // adjust the drawing area for the plot insets (if any)...
2793            RectangleInsets insets = getInsets();
2794            insets.trim(area);
2795    
2796            AxisSpace space = calculateAxisSpace(g2, area);
2797            Rectangle2D dataArea = space.shrink(area, null);
2798            this.axisOffset.trim(dataArea);
2799    
2800            if (info != null) {
2801                info.setDataArea(dataArea);
2802            }
2803    
2804            // draw the plot background and axes...
2805            drawBackground(g2, dataArea);
2806            Map axisStateMap = drawAxes(g2, area, dataArea, info);
2807    
2808            PlotOrientation orient = getOrientation();
2809    
2810            // the anchor point is typically the point where the mouse last
2811            // clicked - the crosshairs will be driven off this point...
2812            if (anchor != null && !dataArea.contains(anchor)) {
2813                anchor = null;
2814            }
2815            CrosshairState crosshairState = new CrosshairState();
2816            crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
2817            crosshairState.setAnchor(anchor);
2818    
2819            crosshairState.setAnchorX(Double.NaN);
2820            crosshairState.setAnchorY(Double.NaN);
2821            if (anchor != null) {
2822                ValueAxis domainAxis = getDomainAxis();
2823                if (domainAxis != null) {
2824                    double x;
2825                    if (orient == PlotOrientation.VERTICAL) {
2826                        x = domainAxis.java2DToValue(anchor.getX(), dataArea,
2827                                getDomainAxisEdge());
2828                    }
2829                    else {
2830                        x = domainAxis.java2DToValue(anchor.getY(), dataArea,
2831                                getDomainAxisEdge());
2832                    }
2833                    crosshairState.setAnchorX(x);
2834                }
2835                ValueAxis rangeAxis = getRangeAxis();
2836                if (rangeAxis != null) {
2837                    double y;
2838                    if (orient == PlotOrientation.VERTICAL) {
2839                        y = rangeAxis.java2DToValue(anchor.getY(), dataArea,
2840                                getRangeAxisEdge());
2841                    }
2842                    else {
2843                        y = rangeAxis.java2DToValue(anchor.getX(), dataArea,
2844                                getRangeAxisEdge());
2845                    }
2846                    crosshairState.setAnchorY(y);
2847                }
2848            }
2849            crosshairState.setCrosshairX(getDomainCrosshairValue());
2850            crosshairState.setCrosshairY(getRangeCrosshairValue());
2851            Shape originalClip = g2.getClip();
2852            Composite originalComposite = g2.getComposite();
2853    
2854            g2.clip(dataArea);
2855            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
2856                    getForegroundAlpha()));
2857    
2858            AxisState domainAxisState = (AxisState) axisStateMap.get(
2859                    getDomainAxis());
2860            if (domainAxisState == null) {
2861                if (parentState != null) {
2862                    domainAxisState = (AxisState) parentState.getSharedAxisStates()
2863                            .get(getDomainAxis());
2864                }
2865            }
2866    
2867            AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis());
2868            if (rangeAxisState == null) {
2869                if (parentState != null) {
2870                    rangeAxisState = (AxisState) parentState.getSharedAxisStates()
2871                            .get(getRangeAxis());
2872                }
2873            }
2874            if (domainAxisState != null) {
2875                drawDomainTickBands(g2, dataArea, domainAxisState.getTicks());
2876            }
2877            if (rangeAxisState != null) {
2878                drawRangeTickBands(g2, dataArea, rangeAxisState.getTicks());
2879            }
2880            if (domainAxisState != null) {
2881                drawDomainGridlines(g2, dataArea, domainAxisState.getTicks());
2882                drawZeroDomainBaseline(g2, dataArea);
2883            }
2884            if (rangeAxisState != null) {
2885                drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
2886                drawZeroRangeBaseline(g2, dataArea);
2887            }
2888    
2889            // draw the markers that are associated with a specific renderer...
2890            for (int i = 0; i < this.renderers.size(); i++) {
2891                drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND);
2892            }
2893            for (int i = 0; i < this.renderers.size(); i++) {
2894                drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND);
2895            }
2896    
2897            // now draw annotations and render data items...
2898            boolean foundData = false;
2899            DatasetRenderingOrder order = getDatasetRenderingOrder();
2900            if (order == DatasetRenderingOrder.FORWARD) {
2901    
2902                // draw background annotations
2903                int rendererCount = this.renderers.size();
2904                for (int i = 0; i < rendererCount; i++) {
2905                    XYItemRenderer r = getRenderer(i);
2906                    if (r != null) {
2907                        ValueAxis domainAxis = getDomainAxisForDataset(i);
2908                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
2909                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
2910                                Layer.BACKGROUND, info);
2911                    }
2912                }
2913    
2914                // render data items...
2915                for (int i = 0; i < getDatasetCount(); i++) {
2916                    foundData = render(g2, dataArea, i, info, crosshairState)
2917                        || foundData;
2918                }
2919    
2920                // draw foreground annotations
2921                for (int i = 0; i < rendererCount; i++) {
2922                    XYItemRenderer r = getRenderer(i);
2923                    if (r != null) {
2924                        ValueAxis domainAxis = getDomainAxisForDataset(i);
2925                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
2926                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
2927                                Layer.FOREGROUND, info);
2928                    }
2929                }
2930    
2931            }
2932            else if (order == DatasetRenderingOrder.REVERSE) {
2933    
2934                // draw background annotations
2935                int rendererCount = this.renderers.size();
2936                for (int i = rendererCount - 1; i >= 0; i--) {
2937                    XYItemRenderer r = getRenderer(i);
2938                    if (i >= getDatasetCount()) { // we need the dataset to make
2939                        continue;                 // a link to the axes
2940                    }
2941                    if (r != null) {
2942                        ValueAxis domainAxis = getDomainAxisForDataset(i);
2943                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
2944                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
2945                                Layer.BACKGROUND, info);
2946                    }
2947                }
2948    
2949                for (int i = getDatasetCount() - 1; i >= 0; i--) {
2950                    foundData = render(g2, dataArea, i, info, crosshairState)
2951                        || foundData;
2952                }
2953    
2954                // draw foreground annotations
2955                for (int i = rendererCount - 1; i >= 0; i--) {
2956                    XYItemRenderer r = getRenderer(i);
2957                    if (i >= getDatasetCount()) { // we need the dataset to make
2958                        continue;                 // a link to the axes
2959                    }
2960                    if (r != null) {
2961                        ValueAxis domainAxis = getDomainAxisForDataset(i);
2962                        ValueAxis rangeAxis = getRangeAxisForDataset(i);
2963                        r.drawAnnotations(g2, dataArea, domainAxis, rangeAxis,
2964                                Layer.FOREGROUND, info);
2965                    }
2966                }
2967    
2968            }
2969    
2970            // draw domain crosshair if required...
2971            int xAxisIndex = crosshairState.getDomainAxisIndex();
2972            ValueAxis xAxis = getDomainAxis(xAxisIndex);
2973            RectangleEdge xAxisEdge = getDomainAxisEdge(xAxisIndex);
2974            if (!this.domainCrosshairLockedOnData && anchor != null) {
2975                double xx;
2976                if (orient == PlotOrientation.VERTICAL) {
2977                    xx = xAxis.java2DToValue(anchor.getX(), dataArea, xAxisEdge);
2978                }
2979                else {
2980                    xx = xAxis.java2DToValue(anchor.getY(), dataArea, xAxisEdge);
2981                }
2982                crosshairState.setCrosshairX(xx);
2983            }
2984            setDomainCrosshairValue(crosshairState.getCrosshairX(), false);
2985            if (isDomainCrosshairVisible()) {
2986                double x = getDomainCrosshairValue();
2987                Paint paint = getDomainCrosshairPaint();
2988                Stroke stroke = getDomainCrosshairStroke();
2989                drawDomainCrosshair(g2, dataArea, orient, x, xAxis, stroke, paint);
2990            }
2991    
2992            // draw range crosshair if required...
2993            int yAxisIndex = crosshairState.getRangeAxisIndex();
2994            ValueAxis yAxis = getRangeAxis(yAxisIndex);
2995            RectangleEdge yAxisEdge = getRangeAxisEdge(yAxisIndex);
2996            if (!this.rangeCrosshairLockedOnData && anchor != null) {
2997                double yy;
2998                if (orient == PlotOrientation.VERTICAL) {
2999                    yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge);
3000                } else {
3001                    yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge);
3002                }
3003                crosshairState.setCrosshairY(yy);
3004            }
3005            setRangeCrosshairValue(crosshairState.getCrosshairY(), false);
3006            if (isRangeCrosshairVisible()) {
3007                double y = getRangeCrosshairValue();
3008                Paint paint = getRangeCrosshairPaint();
3009                Stroke stroke = getRangeCrosshairStroke();
3010                drawRangeCrosshair(g2, dataArea, orient, y, yAxis, stroke, paint);
3011            }
3012    
3013            if (!foundData) {
3014                drawNoDataMessage(g2, dataArea);
3015            }
3016    
3017            for (int i = 0; i < this.renderers.size(); i++) {
3018                drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND);
3019            }
3020            for (int i = 0; i < this.renderers.size(); i++) {
3021                drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND);
3022            }
3023    
3024            drawAnnotations(g2, dataArea, info);
3025            g2.setClip(originalClip);
3026            g2.setComposite(originalComposite);
3027    
3028            drawOutline(g2, dataArea);
3029    
3030        }
3031    
3032        /**
3033         * Draws the background for the plot.
3034         *
3035         * @param g2  the graphics device.
3036         * @param area  the area.
3037         */
3038        public void drawBackground(Graphics2D g2, Rectangle2D area) {
3039            fillBackground(g2, area, this.orientation);
3040            drawQuadrants(g2, area);
3041            drawBackgroundImage(g2, area);
3042        }
3043    
3044        /**
3045         * Draws the quadrants.
3046         *
3047         * @param g2  the graphics device.
3048         * @param area  the area.
3049         *
3050         * @see #setQuadrantOrigin(Point2D)
3051         * @see #setQuadrantPaint(int, Paint)
3052         */
3053        protected void drawQuadrants(Graphics2D g2, Rectangle2D area) {
3054            //  0 | 1
3055            //  --+--
3056            //  2 | 3
3057            boolean somethingToDraw = false;
3058    
3059            ValueAxis xAxis = getDomainAxis();
3060            if (xAxis == null) {  // we can't draw quadrants without a valid x-axis
3061                return;
3062            }
3063            double x = xAxis.getRange().constrain(this.quadrantOrigin.getX());
3064            double xx = xAxis.valueToJava2D(x, area, getDomainAxisEdge());
3065    
3066            ValueAxis yAxis = getRangeAxis();
3067            if (yAxis == null) {  // we can't draw quadrants without a valid y-axis
3068                return;
3069            }
3070            double y = yAxis.getRange().constrain(this.quadrantOrigin.getY());
3071            double yy = yAxis.valueToJava2D(y, area, getRangeAxisEdge());
3072    
3073            double xmin = xAxis.getLowerBound();
3074            double xxmin = xAxis.valueToJava2D(xmin, area, getDomainAxisEdge());
3075    
3076            double xmax = xAxis.getUpperBound();
3077            double xxmax = xAxis.valueToJava2D(xmax, area, getDomainAxisEdge());
3078    
3079            double ymin = yAxis.getLowerBound();
3080            double yymin = yAxis.valueToJava2D(ymin, area, getRangeAxisEdge());
3081    
3082            double ymax = yAxis.getUpperBound();
3083            double yymax = yAxis.valueToJava2D(ymax, area, getRangeAxisEdge());
3084    
3085            Rectangle2D[] r = new Rectangle2D[] {null, null, null, null};
3086            if (this.quadrantPaint[0] != null) {
3087                if (x > xmin && y < ymax) {
3088                    if (this.orientation == PlotOrientation.HORIZONTAL) {
3089                        r[0] = new Rectangle2D.Double(Math.min(yymax, yy),
3090                                Math.min(xxmin, xx), Math.abs(yy - yymax),
3091                                Math.abs(xx - xxmin));
3092                    }
3093                    else {  // PlotOrientation.VERTICAL
3094                        r[0] = new Rectangle2D.Double(Math.min(xxmin, xx),
3095                                Math.min(yymax, yy), Math.abs(xx - xxmin),
3096                                Math.abs(yy - yymax));
3097                    }
3098                    somethingToDraw = true;
3099                }
3100            }
3101            if (this.quadrantPaint[1] != null) {
3102                if (x < xmax && y < ymax) {
3103                    if (this.orientation == PlotOrientation.HORIZONTAL) {
3104                        r[1] = new Rectangle2D.Double(Math.min(yymax, yy),
3105                                Math.min(xxmax, xx), Math.abs(yy - yymax),
3106                                Math.abs(xx - xxmax));
3107                    }
3108                    else {  // PlotOrientation.VERTICAL
3109                        r[1] = new Rectangle2D.Double(Math.min(xx, xxmax),
3110                                Math.min(yymax, yy), Math.abs(xx - xxmax),
3111                                Math.abs(yy - yymax));
3112                    }
3113                    somethingToDraw = true;
3114                }
3115            }
3116            if (this.quadrantPaint[2] != null) {
3117                if (x > xmin && y > ymin) {
3118                    if (this.orientation == PlotOrientation.HORIZONTAL) {
3119                        r[2] = new Rectangle2D.Double(Math.min(yymin, yy),
3120                                Math.min(xxmin, xx), Math.abs(yy - yymin),
3121                                Math.abs(xx - xxmin));
3122                    }
3123                    else {  // PlotOrientation.VERTICAL
3124                        r[2] = new Rectangle2D.Double(Math.min(xxmin, xx),
3125                                Math.min(yymin, yy), Math.abs(xx - xxmin),
3126                                Math.abs(yy - yymin));
3127                    }
3128                    somethingToDraw = true;
3129                }
3130            }
3131            if (this.quadrantPaint[3] != null) {
3132                if (x < xmax && y > ymin) {
3133                    if (this.orientation == PlotOrientation.HORIZONTAL) {
3134                        r[3] = new Rectangle2D.Double(Math.min(yymin, yy),
3135                                Math.min(xxmax, xx), Math.abs(yy - yymin),
3136                                Math.abs(xx - xxmax));
3137                    }
3138                    else {  // PlotOrientation.VERTICAL
3139                        r[3] = new Rectangle2D.Double(Math.min(xx, xxmax),
3140                                Math.min(yymin, yy), Math.abs(xx - xxmax),
3141                                Math.abs(yy - yymin));
3142                    }
3143                    somethingToDraw = true;
3144                }
3145            }
3146            if (somethingToDraw) {
3147                Composite originalComposite = g2.getComposite();
3148                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
3149                        getBackgroundAlpha()));
3150                for (int i = 0; i < 4; i++) {
3151                    if (this.quadrantPaint[i] != null && r[i] != null) {
3152                        g2.setPaint(this.quadrantPaint[i]);
3153                        g2.fill(r[i]);
3154                    }
3155                }
3156                g2.setComposite(originalComposite);
3157            }
3158        }
3159    
3160        /**
3161         * Draws the domain tick bands, if any.
3162         *
3163         * @param g2  the graphics device.
3164         * @param dataArea  the data area.
3165         * @param ticks  the ticks.
3166         *
3167         * @see #setDomainTickBandPaint(Paint)
3168         */
3169        public void drawDomainTickBands(Graphics2D g2, Rectangle2D dataArea,
3170                                        List ticks) {
3171            Paint bandPaint = getDomainTickBandPaint();
3172            if (bandPaint != null) {
3173                boolean fillBand = false;
3174                ValueAxis xAxis = getDomainAxis();
3175                double previous = xAxis.getLowerBound();
3176                Iterator iterator = ticks.iterator();
3177                while (iterator.hasNext()) {
3178                    ValueTick tick = (ValueTick) iterator.next();
3179                    double current = tick.getValue();
3180                    if (fillBand) {
3181                        getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea,
3182                                previous, current);
3183                    }
3184                    previous = current;
3185                    fillBand = !fillBand;
3186                }
3187                double end = xAxis.getUpperBound();
3188                if (fillBand) {
3189                    getRenderer().fillDomainGridBand(g2, this, xAxis, dataArea,
3190                            previous, end);
3191                }
3192            }
3193        }
3194    
3195        /**
3196         * Draws the range tick bands, if any.
3197         *
3198         * @param g2  the graphics device.
3199         * @param dataArea  the data area.
3200         * @param ticks  the ticks.
3201         *
3202         * @see #setRangeTickBandPaint(Paint)
3203         */
3204        public void drawRangeTickBands(Graphics2D g2, Rectangle2D dataArea,
3205                                       List ticks) {
3206            Paint bandPaint = getRangeTickBandPaint();
3207            if (bandPaint != null) {
3208                boolean fillBand = false;
3209                ValueAxis axis = getRangeAxis();
3210                double previous = axis.getLowerBound();
3211                Iterator iterator = ticks.iterator();
3212                while (iterator.hasNext()) {
3213                    ValueTick tick = (ValueTick) iterator.next();
3214                    double current = tick.getValue();
3215                    if (fillBand) {
3216                        getRenderer().fillRangeGridBand(g2, this, axis, dataArea,
3217                                previous, current);
3218                    }
3219                    previous = current;
3220                    fillBand = !fillBand;
3221                }
3222                double end = axis.getUpperBound();
3223                if (fillBand) {
3224                    getRenderer().fillRangeGridBand(g2, this, axis, dataArea,
3225                            previous, end);
3226                }
3227            }
3228        }
3229    
3230        /**
3231         * A utility method for drawing the axes.
3232         *
3233         * @param g2  the graphics device (<code>null</code> not permitted).
3234         * @param plotArea  the plot area (<code>null</code> not permitted).
3235         * @param dataArea  the data area (<code>null</code> not permitted).
3236         * @param plotState  collects information about the plot (<code>null</code>
3237         *                   permitted).
3238         *
3239         * @return A map containing the state for each axis drawn.
3240         */
3241        protected Map drawAxes(Graphics2D g2,
3242                               Rectangle2D plotArea,
3243                               Rectangle2D dataArea,
3244                               PlotRenderingInfo plotState) {
3245    
3246            AxisCollection axisCollection = new AxisCollection();
3247    
3248            // add domain axes to lists...
3249            for (int index = 0; index < this.domainAxes.size(); index++) {
3250                ValueAxis axis = (ValueAxis) this.domainAxes.get(index);
3251                if (axis != null) {
3252                    axisCollection.add(axis, getDomainAxisEdge(index));
3253                }
3254            }
3255    
3256            // add range axes to lists...
3257            for (int index = 0; index < this.rangeAxes.size(); index++) {
3258                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
3259                if (yAxis != null) {
3260                    axisCollection.add(yAxis, getRangeAxisEdge(index));
3261                }
3262            }
3263    
3264            Map axisStateMap = new HashMap();
3265    
3266            // draw the top axes
3267            double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
3268                    dataArea.getHeight());
3269            Iterator iterator = axisCollection.getAxesAtTop().iterator();
3270            while (iterator.hasNext()) {
3271                ValueAxis axis = (ValueAxis) iterator.next();
3272                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
3273                        RectangleEdge.TOP, plotState);
3274                cursor = info.getCursor();
3275                axisStateMap.put(axis, info);
3276            }
3277    
3278            // draw the bottom axes
3279            cursor = dataArea.getMaxY()
3280                     + this.axisOffset.calculateBottomOutset(dataArea.getHeight());
3281            iterator = axisCollection.getAxesAtBottom().iterator();
3282            while (iterator.hasNext()) {
3283                ValueAxis axis = (ValueAxis) iterator.next();
3284                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
3285                        RectangleEdge.BOTTOM, plotState);
3286                cursor = info.getCursor();
3287                axisStateMap.put(axis, info);
3288            }
3289    
3290            // draw the left axes
3291            cursor = dataArea.getMinX()
3292                     - this.axisOffset.calculateLeftOutset(dataArea.getWidth());
3293            iterator = axisCollection.getAxesAtLeft().iterator();
3294            while (iterator.hasNext()) {
3295                ValueAxis axis = (ValueAxis) iterator.next();
3296                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
3297                        RectangleEdge.LEFT, plotState);
3298                cursor = info.getCursor();
3299                axisStateMap.put(axis, info);
3300            }
3301    
3302            // draw the right axes
3303            cursor = dataArea.getMaxX()
3304                     + this.axisOffset.calculateRightOutset(dataArea.getWidth());
3305            iterator = axisCollection.getAxesAtRight().iterator();
3306            while (iterator.hasNext()) {
3307                ValueAxis axis = (ValueAxis) iterator.next();
3308                AxisState info = axis.draw(g2, cursor, plotArea, dataArea,
3309                        RectangleEdge.RIGHT, plotState);
3310                cursor = info.getCursor();
3311                axisStateMap.put(axis, info);
3312            }
3313    
3314            return axisStateMap;
3315        }
3316    
3317        /**
3318         * Draws a representation of the data within the dataArea region, using the
3319         * current renderer.
3320         * <P>
3321         * The <code>info</code> and <code>crosshairState</code> arguments may be
3322         * <code>null</code>.
3323         *
3324         * @param g2  the graphics device.
3325         * @param dataArea  the region in which the data is to be drawn.
3326         * @param index  the dataset index.
3327         * @param info  an optional object for collection dimension information.
3328         * @param crosshairState  collects crosshair information
3329         *                        (<code>null</code> permitted).
3330         *
3331         * @return A flag that indicates whether any data was actually rendered.
3332         */
3333        public boolean render(Graphics2D g2, Rectangle2D dataArea, int index,
3334                PlotRenderingInfo info, CrosshairState crosshairState) {
3335    
3336            boolean foundData = false;
3337            XYDataset dataset = getDataset(index);
3338            if (!DatasetUtilities.isEmptyOrNull(dataset)) {
3339                foundData = true;
3340                ValueAxis xAxis = getDomainAxisForDataset(index);
3341                ValueAxis yAxis = getRangeAxisForDataset(index);
3342                if (xAxis == null || yAxis == null) {
3343                    return foundData;  // can't render anything without axes
3344                }
3345                XYItemRenderer renderer = getRenderer(index);
3346                if (renderer == null) {
3347                    renderer = getRenderer();
3348                    if (renderer == null) { // no default renderer available
3349                        return foundData;
3350                    }
3351                }
3352    
3353                XYItemRendererState state = renderer.initialise(g2, dataArea, this,
3354                        dataset, info);
3355                int passCount = renderer.getPassCount();
3356    
3357                SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();
3358                if (seriesOrder == SeriesRenderingOrder.REVERSE) {
3359                    //render series in reverse order
3360                    for (int pass = 0; pass < passCount; pass++) {
3361                        int seriesCount = dataset.getSeriesCount();
3362                        for (int series = seriesCount - 1; series >= 0; series--) {
3363                            int firstItem = 0;
3364                            int lastItem = dataset.getItemCount(series) - 1;
3365                            if (lastItem == -1) {
3366                                continue;
3367                            }
3368                            if (state.getProcessVisibleItemsOnly()) {
3369                                int[] itemBounds = RendererUtilities.findLiveItems(
3370                                        dataset, series, xAxis.getLowerBound(),
3371                                        xAxis.getUpperBound());
3372                                firstItem = itemBounds[0];
3373                                lastItem = itemBounds[1];
3374                            }
3375                            state.startSeriesPass(dataset, series, firstItem,
3376                                    lastItem, pass, passCount);
3377                            for (int item = firstItem; item <= lastItem; item++) {
3378                                renderer.drawItem(g2, state, dataArea, info,
3379                                        this, xAxis, yAxis, dataset, series, item,
3380                                        crosshairState, pass);
3381                            }
3382                            state.endSeriesPass(dataset, series, firstItem,
3383                                    lastItem, pass, passCount);
3384                        }
3385                    }
3386                }
3387                else {
3388                    //render series in forward order
3389                    for (int pass = 0; pass < passCount; pass++) {
3390                        int seriesCount = dataset.getSeriesCount();
3391                        for (int series = 0; series < seriesCount; series++) {
3392                            int firstItem = 0;
3393                            int lastItem = dataset.getItemCount(series) - 1;
3394                            if (state.getProcessVisibleItemsOnly()) {
3395                                int[] itemBounds = RendererUtilities.findLiveItems(
3396                                        dataset, series, xAxis.getLowerBound(),
3397                                        xAxis.getUpperBound());
3398                                firstItem = itemBounds[0];
3399                                lastItem = itemBounds[1];
3400                            }
3401                            state.startSeriesPass(dataset, series, firstItem,
3402                                    lastItem, pass, passCount);
3403                            for (int item = firstItem; item <= lastItem; item++) {
3404                                renderer.drawItem(g2, state, dataArea, info,
3405                                        this, xAxis, yAxis, dataset, series, item,
3406                                        crosshairState, pass);
3407                            }
3408                            state.endSeriesPass(dataset, series, firstItem,
3409                                    lastItem, pass, passCount);
3410                        }
3411                    }
3412                }
3413            }
3414            return foundData;
3415        }
3416    
3417        /**
3418         * Returns the domain axis for a dataset.
3419         *
3420         * @param index  the dataset index.
3421         *
3422         * @return The axis.
3423         */
3424        public ValueAxis getDomainAxisForDataset(int index) {
3425    
3426            if (index < 0 || index >= getDatasetCount()) {
3427                throw new IllegalArgumentException("Index " + index
3428                        + " out of bounds.");
3429            }
3430    
3431            ValueAxis valueAxis = null;
3432            Integer axisIndex = (Integer) this.datasetToDomainAxisMap.get(
3433                    new Integer(index));
3434            if (axisIndex != null) {
3435                valueAxis = getDomainAxis(axisIndex.intValue());
3436            }
3437            else {
3438                valueAxis = getDomainAxis(0);
3439            }
3440            return valueAxis;
3441    
3442        }
3443    
3444        /**
3445         * Returns the range axis for a dataset.
3446         *
3447         * @param index  the dataset index.
3448         *
3449         * @return The axis.
3450         */
3451        public ValueAxis getRangeAxisForDataset(int index) {
3452    
3453            if (index < 0 || index >= getDatasetCount()) {
3454                throw new IllegalArgumentException("Index " + index
3455                        + " out of bounds.");
3456            }
3457    
3458            ValueAxis valueAxis = null;
3459            Integer axisIndex
3460                = (Integer) this.datasetToRangeAxisMap.get(new Integer(index));
3461            if (axisIndex != null) {
3462                valueAxis = getRangeAxis(axisIndex.intValue());
3463            }
3464            else {
3465                valueAxis = getRangeAxis(0);
3466            }
3467            return valueAxis;
3468    
3469        }
3470    
3471        /**
3472         * Draws the gridlines for the plot, if they are visible.
3473         *
3474         * @param g2  the graphics device.
3475         * @param dataArea  the data area.
3476         * @param ticks  the ticks.
3477         *
3478         * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List)
3479         */
3480        protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea,
3481                                           List ticks) {
3482    
3483            // no renderer, no gridlines...
3484            if (getRenderer() == null) {
3485                return;
3486            }
3487    
3488            // draw the domain grid lines, if any...
3489            if (isDomainGridlinesVisible()) {
3490                Stroke gridStroke = getDomainGridlineStroke();
3491                Paint gridPaint = getDomainGridlinePaint();
3492                if ((gridStroke != null) && (gridPaint != null)) {
3493                    Iterator iterator = ticks.iterator();
3494                    while (iterator.hasNext()) {
3495                        ValueTick tick = (ValueTick) iterator.next();
3496                        getRenderer().drawDomainGridLine(g2, this, getDomainAxis(),
3497                                dataArea, tick.getValue());
3498                    }
3499                }
3500            }
3501        }
3502    
3503        /**
3504         * Draws the gridlines for the plot's primary range axis, if they are
3505         * visible.
3506         *
3507         * @param g2  the graphics device.
3508         * @param area  the data area.
3509         * @param ticks  the ticks.
3510         *
3511         * @see #drawDomainGridlines(Graphics2D, Rectangle2D, List)
3512         */
3513        protected void drawRangeGridlines(Graphics2D g2, Rectangle2D area,
3514                                          List ticks) {
3515    
3516            // no renderer, no gridlines...
3517            if (getRenderer() == null) {
3518                return;
3519            }
3520    
3521            // draw the range grid lines, if any...
3522            if (isRangeGridlinesVisible()) {
3523                Stroke gridStroke = getRangeGridlineStroke();
3524                Paint gridPaint = getRangeGridlinePaint();
3525                ValueAxis axis = getRangeAxis();
3526                if (axis != null) {
3527                    Iterator iterator = ticks.iterator();
3528                    while (iterator.hasNext()) {
3529                        ValueTick tick = (ValueTick) iterator.next();
3530                        if (tick.getValue() != 0.0
3531                                || !isRangeZeroBaselineVisible()) {
3532                            getRenderer().drawRangeLine(g2, this, getRangeAxis(),
3533                                    area, tick.getValue(), gridPaint, gridStroke);
3534                        }
3535                    }
3536                }
3537            }
3538        }
3539    
3540        /**
3541         * Draws a base line across the chart at value zero on the domain axis.
3542         *
3543         * @param g2  the graphics device.
3544         * @param area  the data area.
3545         *
3546         * @see #setDomainZeroBaselineVisible(boolean)
3547         *
3548         * @since 1.0.5
3549         */
3550        protected void drawZeroDomainBaseline(Graphics2D g2, Rectangle2D area) {
3551            if (isDomainZeroBaselineVisible()) {
3552                XYItemRenderer r = getRenderer();
3553                // FIXME: the renderer interface doesn't have the drawDomainLine()
3554                // method, so we have to rely on the renderer being a subclass of
3555                // AbstractXYItemRenderer (which is lame)
3556                if (r instanceof AbstractXYItemRenderer) {
3557                    AbstractXYItemRenderer renderer = (AbstractXYItemRenderer) r;
3558                    renderer.drawDomainLine(g2, this, getDomainAxis(), area, 0.0,
3559                            this.domainZeroBaselinePaint,
3560                            this.domainZeroBaselineStroke);
3561                }
3562            }
3563        }
3564    
3565        /**
3566         * Draws a base line across the chart at value zero on the range axis.
3567         *
3568         * @param g2  the graphics device.
3569         * @param area  the data area.
3570         *
3571         * @see #setRangeZeroBaselineVisible(boolean)
3572         */
3573        protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) {
3574            if (isRangeZeroBaselineVisible()) {
3575                getRenderer().drawRangeLine(g2, this, getRangeAxis(), area, 0.0,
3576                        this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke);
3577            }
3578        }
3579    
3580        /**
3581         * Draws the annotations for the plot.
3582         *
3583         * @param g2  the graphics device.
3584         * @param dataArea  the data area.
3585         * @param info  the chart rendering info.
3586         */
3587        public void drawAnnotations(Graphics2D g2,
3588                                    Rectangle2D dataArea,
3589                                    PlotRenderingInfo info) {
3590    
3591            Iterator iterator = this.annotations.iterator();
3592            while (iterator.hasNext()) {
3593                XYAnnotation annotation = (XYAnnotation) iterator.next();
3594                ValueAxis xAxis = getDomainAxis();
3595                ValueAxis yAxis = getRangeAxis();
3596                annotation.draw(g2, this, dataArea, xAxis, yAxis, 0, info);
3597            }
3598    
3599        }
3600    
3601        /**
3602         * Draws the domain markers (if any) for an axis and layer.  This method is
3603         * typically called from within the draw() method.
3604         *
3605         * @param g2  the graphics device.
3606         * @param dataArea  the data area.
3607         * @param index  the renderer index.
3608         * @param layer  the layer (foreground or background).
3609         */
3610        protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea,
3611                                         int index, Layer layer) {
3612    
3613            XYItemRenderer r = getRenderer(index);
3614            if (r == null) {
3615                return;
3616            }
3617            // check that the renderer has a corresponding dataset (it doesn't
3618            // matter if the dataset is null)
3619            if (index >= getDatasetCount()) {
3620                return;
3621            }
3622            Collection markers = getDomainMarkers(index, layer);
3623            ValueAxis axis = getDomainAxisForDataset(index);
3624            if (markers != null && axis != null) {
3625                Iterator iterator = markers.iterator();
3626                while (iterator.hasNext()) {
3627                    Marker marker = (Marker) iterator.next();
3628                    r.drawDomainMarker(g2, this, axis, marker, dataArea);
3629                }
3630            }
3631    
3632        }
3633    
3634        /**
3635         * Draws the range markers (if any) for a renderer and layer.  This method
3636         * is typically called from within the draw() method.
3637         *
3638         * @param g2  the graphics device.
3639         * @param dataArea  the data area.
3640         * @param index  the renderer index.
3641         * @param layer  the layer (foreground or background).
3642         */
3643        protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea,
3644                                        int index, Layer layer) {
3645    
3646            XYItemRenderer r = getRenderer(index);
3647            if (r == null) {
3648                return;
3649            }
3650            // check that the renderer has a corresponding dataset (it doesn't
3651            // matter if the dataset is null)
3652            if (index >= getDatasetCount()) {
3653                return;
3654            }
3655            Collection markers = getRangeMarkers(index, layer);
3656            ValueAxis axis = getRangeAxisForDataset(index);
3657            if (markers != null && axis != null) {
3658                Iterator iterator = markers.iterator();
3659                while (iterator.hasNext()) {
3660                    Marker marker = (Marker) iterator.next();
3661                    r.drawRangeMarker(g2, this, axis, marker, dataArea);
3662                }
3663            }
3664        }
3665    
3666        /**
3667         * Returns the list of domain markers (read only) for the specified layer.
3668         *
3669         * @param layer  the layer (foreground or background).
3670         *
3671         * @return The list of domain markers.
3672         *
3673         * @see #getRangeMarkers(Layer)
3674         */
3675        public Collection getDomainMarkers(Layer layer) {
3676            return getDomainMarkers(0, layer);
3677        }
3678    
3679        /**
3680         * Returns the list of range markers (read only) for the specified layer.
3681         *
3682         * @param layer  the layer (foreground or background).
3683         *
3684         * @return The list of range markers.
3685         *
3686         * @see #getDomainMarkers(Layer)
3687         */
3688        public Collection getRangeMarkers(Layer layer) {
3689            return getRangeMarkers(0, layer);
3690        }
3691    
3692        /**
3693         * Returns a collection of domain markers for a particular renderer and
3694         * layer.
3695         *
3696         * @param index  the renderer index.
3697         * @param layer  the layer.
3698         *
3699         * @return A collection of markers (possibly <code>null</code>).
3700         *
3701         * @see #getRangeMarkers(int, Layer)
3702         */
3703        public Collection getDomainMarkers(int index, Layer layer) {
3704            Collection result = null;
3705            Integer key = new Integer(index);
3706            if (layer == Layer.FOREGROUND) {
3707                result = (Collection) this.foregroundDomainMarkers.get(key);
3708            }
3709            else if (layer == Layer.BACKGROUND) {
3710                result = (Collection) this.backgroundDomainMarkers.get(key);
3711            }
3712            if (result != null) {
3713                result = Collections.unmodifiableCollection(result);
3714            }
3715            return result;
3716        }
3717    
3718        /**
3719         * Returns a collection of range markers for a particular renderer and
3720         * layer.
3721         *
3722         * @param index  the renderer index.
3723         * @param layer  the layer.
3724         *
3725         * @return A collection of markers (possibly <code>null</code>).
3726         *
3727         * @see #getDomainMarkers(int, Layer)
3728         */
3729        public Collection getRangeMarkers(int index, Layer layer) {
3730            Collection result = null;
3731            Integer key = new Integer(index);
3732            if (layer == Layer.FOREGROUND) {
3733                result = (Collection) this.foregroundRangeMarkers.get(key);
3734            }
3735            else if (layer == Layer.BACKGROUND) {
3736                result = (Collection) this.backgroundRangeMarkers.get(key);
3737            }
3738            if (result != null) {
3739                result = Collections.unmodifiableCollection(result);
3740            }
3741            return result;
3742        }
3743    
3744        /**
3745         * Utility method for drawing a horizontal line across the data area of the
3746         * plot.
3747         *
3748         * @param g2  the graphics device.
3749         * @param dataArea  the data area.
3750         * @param value  the coordinate, where to draw the line.
3751         * @param stroke  the stroke to use.
3752         * @param paint  the paint to use.
3753         */
3754        protected void drawHorizontalLine(Graphics2D g2, Rectangle2D dataArea,
3755                                          double value, Stroke stroke,
3756                                          Paint paint) {
3757    
3758            ValueAxis axis = getRangeAxis();
3759            if (getOrientation() == PlotOrientation.HORIZONTAL) {
3760                axis = getDomainAxis();
3761            }
3762            if (axis.getRange().contains(value)) {
3763                double yy = axis.valueToJava2D(value, dataArea, RectangleEdge.LEFT);
3764                Line2D line = new Line2D.Double(dataArea.getMinX(), yy,
3765                        dataArea.getMaxX(), yy);
3766                g2.setStroke(stroke);
3767                g2.setPaint(paint);
3768                g2.draw(line);
3769            }
3770    
3771        }
3772    
3773        /**
3774         * Draws a domain crosshair.
3775         *
3776         * @param g2  the graphics target.
3777         * @param dataArea  the data area.
3778         * @param orientation  the plot orientation.
3779         * @param value  the crosshair value.
3780         * @param axis  the axis against which the value is measured.
3781         * @param stroke  the stroke used to draw the crosshair line.
3782         * @param paint  the paint used to draw the crosshair line.
3783         *
3784         * @since 1.0.4
3785         */
3786        protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea,
3787                PlotOrientation orientation, double value, ValueAxis axis,
3788                Stroke stroke, Paint paint) {
3789    
3790            if (axis.getRange().contains(value)) {
3791                Line2D line = null;
3792                if (orientation == PlotOrientation.VERTICAL) {
3793                    double xx = axis.valueToJava2D(value, dataArea,
3794                            RectangleEdge.BOTTOM);
3795                    line = new Line2D.Double(xx, dataArea.getMinY(), xx,
3796                            dataArea.getMaxY());
3797                }
3798                else {
3799                    double yy = axis.valueToJava2D(value, dataArea,
3800                            RectangleEdge.LEFT);
3801                    line = new Line2D.Double(dataArea.getMinX(), yy,
3802                            dataArea.getMaxX(), yy);
3803                }
3804                g2.setStroke(stroke);
3805                g2.setPaint(paint);
3806                g2.draw(line);
3807            }
3808    
3809        }
3810    
3811        /**
3812         * Utility method for drawing a vertical line on the data area of the plot.
3813         *
3814         * @param g2  the graphics device.
3815         * @param dataArea  the data area.
3816         * @param value  the coordinate, where to draw the line.
3817         * @param stroke  the stroke to use.
3818         * @param paint  the paint to use.
3819         */
3820        protected void drawVerticalLine(Graphics2D g2, Rectangle2D dataArea,
3821                                        double value, Stroke stroke, Paint paint) {
3822    
3823            ValueAxis axis = getDomainAxis();
3824            if (getOrientation() == PlotOrientation.HORIZONTAL) {
3825                axis = getRangeAxis();
3826            }
3827            if (axis.getRange().contains(value)) {
3828                double xx = axis.valueToJava2D(value, dataArea,
3829                        RectangleEdge.BOTTOM);
3830                Line2D line = new Line2D.Double(xx, dataArea.getMinY(), xx,
3831                        dataArea.getMaxY());
3832                g2.setStroke(stroke);
3833                g2.setPaint(paint);
3834                g2.draw(line);
3835            }
3836    
3837        }
3838    
3839        /**
3840         * Draws a range crosshair.
3841         *
3842         * @param g2  the graphics target.
3843         * @param dataArea  the data area.
3844         * @param orientation  the plot orientation.
3845         * @param value  the crosshair value.
3846         * @param axis  the axis against which the value is measured.
3847         * @param stroke  the stroke used to draw the crosshair line.
3848         * @param paint  the paint used to draw the crosshair line.
3849         *
3850         * @since 1.0.4
3851         */
3852        protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea,
3853                PlotOrientation orientation, double value, ValueAxis axis,
3854                Stroke stroke, Paint paint) {
3855    
3856            if (axis.getRange().contains(value)) {
3857                Line2D line = null;
3858                if (orientation == PlotOrientation.HORIZONTAL) {
3859                    double xx = axis.valueToJava2D(value, dataArea,
3860                            RectangleEdge.BOTTOM);
3861                    line = new Line2D.Double(xx, dataArea.getMinY(), xx,
3862                            dataArea.getMaxY());
3863                }
3864                else {
3865                    double yy = axis.valueToJava2D(value, dataArea,
3866                            RectangleEdge.LEFT);
3867                    line = new Line2D.Double(dataArea.getMinX(), yy,
3868                            dataArea.getMaxX(), yy);
3869                }
3870                g2.setStroke(stroke);
3871                g2.setPaint(paint);
3872                g2.draw(line);
3873            }
3874    
3875        }
3876    
3877        /**
3878         * Handles a 'click' on the plot by updating the anchor values.
3879         *
3880         * @param x  the x-coordinate, where the click occurred, in Java2D space.
3881         * @param y  the y-coordinate, where the click occurred, in Java2D space.
3882         * @param info  object containing information about the plot dimensions.
3883         */
3884        public void handleClick(int x, int y, PlotRenderingInfo info) {
3885    
3886            Rectangle2D dataArea = info.getDataArea();
3887            if (dataArea.contains(x, y)) {
3888                // set the anchor value for the horizontal axis...
3889                ValueAxis xaxis = getDomainAxis();
3890                if (xaxis != null) {
3891                    double hvalue = xaxis.java2DToValue(x, info.getDataArea(),
3892                            getDomainAxisEdge());
3893                    setDomainCrosshairValue(hvalue);
3894                }
3895    
3896                // set the anchor value for the vertical axis...
3897                ValueAxis yaxis = getRangeAxis();
3898                if (yaxis != null) {
3899                    double vvalue = yaxis.java2DToValue(y, info.getDataArea(),
3900                            getRangeAxisEdge());
3901                    setRangeCrosshairValue(vvalue);
3902                }
3903            }
3904        }
3905    
3906        /**
3907         * A utility method that returns a list of datasets that are mapped to a
3908         * particular axis.
3909         *
3910         * @param axisIndex  the axis index (<code>null</code> not permitted).
3911         *
3912         * @return A list of datasets.
3913         */
3914        private List getDatasetsMappedToDomainAxis(Integer axisIndex) {
3915            if (axisIndex == null) {
3916                throw new IllegalArgumentException("Null 'axisIndex' argument.");
3917            }
3918            List result = new ArrayList();
3919            for (int i = 0; i < this.datasets.size(); i++) {
3920                Integer mappedAxis = (Integer) this.datasetToDomainAxisMap.get(
3921                        new Integer(i));
3922                if (mappedAxis == null) {
3923                    if (axisIndex.equals(ZERO)) {
3924                        result.add(this.datasets.get(i));
3925                    }
3926                }
3927                else {
3928                    if (mappedAxis.equals(axisIndex)) {
3929                        result.add(this.datasets.get(i));
3930                    }
3931                }
3932            }
3933            return result;
3934        }
3935    
3936        /**
3937         * A utility method that returns a list of datasets that are mapped to a
3938         * particular axis.
3939         *
3940         * @param axisIndex  the axis index (<code>null</code> not permitted).
3941         *
3942         * @return A list of datasets.
3943         */
3944        private List getDatasetsMappedToRangeAxis(Integer axisIndex) {
3945            if (axisIndex == null) {
3946                throw new IllegalArgumentException("Null 'axisIndex' argument.");
3947            }
3948            List result = new ArrayList();
3949            for (int i = 0; i < this.datasets.size(); i++) {
3950                Integer mappedAxis = (Integer) this.datasetToRangeAxisMap.get(
3951                        new Integer(i));
3952                if (mappedAxis == null) {
3953                    if (axisIndex.equals(ZERO)) {
3954                        result.add(this.datasets.get(i));
3955                    }
3956                }
3957                else {
3958                    if (mappedAxis.equals(axisIndex)) {
3959                        result.add(this.datasets.get(i));
3960                    }
3961                }
3962            }
3963            return result;
3964        }
3965    
3966        /**
3967         * Returns the index of the given domain axis.
3968         *
3969         * @param axis  the axis.
3970         *
3971         * @return The axis index.
3972         *
3973         * @see #getRangeAxisIndex(ValueAxis)
3974         */
3975        public int getDomainAxisIndex(ValueAxis axis) {
3976            int result = this.domainAxes.indexOf(axis);
3977            if (result < 0) {
3978                // try the parent plot
3979                Plot parent = getParent();
3980                if (parent instanceof XYPlot) {
3981                    XYPlot p = (XYPlot) parent;
3982                    result = p.getDomainAxisIndex(axis);
3983                }
3984            }
3985            return result;
3986        }
3987    
3988        /**
3989         * Returns the index of the given range axis.
3990         *
3991         * @param axis  the axis.
3992         *
3993         * @return The axis index.
3994         *
3995         * @see #getDomainAxisIndex(ValueAxis)
3996         */
3997        public int getRangeAxisIndex(ValueAxis axis) {
3998            int result = this.rangeAxes.indexOf(axis);
3999            if (result < 0) {
4000                // try the parent plot
4001                Plot parent = getParent();
4002                if (parent instanceof XYPlot) {
4003                    XYPlot p = (XYPlot) parent;
4004                    result = p.getRangeAxisIndex(axis);
4005                }
4006            }
4007            return result;
4008        }
4009    
4010        /**
4011         * Returns the range for the specified axis.
4012         *
4013         * @param axis  the axis.
4014         *
4015         * @return The range.
4016         */
4017        public Range getDataRange(ValueAxis axis) {
4018    
4019            Range result = null;
4020            List mappedDatasets = new ArrayList();
4021            boolean isDomainAxis = true;
4022    
4023            // is it a domain axis?
4024            int domainIndex = getDomainAxisIndex(axis);
4025            if (domainIndex >= 0) {
4026                isDomainAxis = true;
4027                mappedDatasets.addAll(getDatasetsMappedToDomainAxis(
4028                        new Integer(domainIndex)));
4029            }
4030    
4031            // or is it a range axis?
4032            int rangeIndex = getRangeAxisIndex(axis);
4033            if (rangeIndex >= 0) {
4034                isDomainAxis = false;
4035                mappedDatasets.addAll(getDatasetsMappedToRangeAxis(
4036                        new Integer(rangeIndex)));
4037            }
4038    
4039            // iterate through the datasets that map to the axis and get the union
4040            // of the ranges.
4041            Iterator iterator = mappedDatasets.iterator();
4042            while (iterator.hasNext()) {
4043                XYDataset d = (XYDataset) iterator.next();
4044                if (d != null) {
4045                    XYItemRenderer r = getRendererForDataset(d);
4046                    if (isDomainAxis) {
4047                        if (r != null) {
4048                            result = Range.combine(result, r.findDomainBounds(d));
4049                        }
4050                        else {
4051                            result = Range.combine(result,
4052                                    DatasetUtilities.findDomainBounds(d));
4053                        }
4054                    }
4055                    else {
4056                        if (r != null) {
4057                            result = Range.combine(result, r.findRangeBounds(d));
4058                        }
4059                        else {
4060                            result = Range.combine(result,
4061                                    DatasetUtilities.findRangeBounds(d));
4062                        }
4063                    }
4064                }
4065            }
4066            return result;
4067    
4068        }
4069    
4070        /**
4071         * Receives notification of a change to the plot's dataset.
4072         * <P>
4073         * The axis ranges are updated if necessary.
4074         *
4075         * @param event  information about the event (not used here).
4076         */
4077        public void datasetChanged(DatasetChangeEvent event) {
4078            configureDomainAxes();
4079            configureRangeAxes();
4080            if (getParent() != null) {
4081                getParent().datasetChanged(event);
4082            }
4083            else {
4084                PlotChangeEvent e = new PlotChangeEvent(this);
4085                e.setType(ChartChangeEventType.DATASET_UPDATED);
4086                notifyListeners(e);
4087            }
4088        }
4089    
4090        /**
4091         * Receives notification of a renderer change event.
4092         *
4093         * @param event  the event.
4094         */
4095        public void rendererChanged(RendererChangeEvent event) {
4096            fireChangeEvent();
4097        }
4098    
4099        /**
4100         * Returns a flag indicating whether or not the domain crosshair is visible.
4101         *
4102         * @return The flag.
4103         *
4104         * @see #setDomainCrosshairVisible(boolean)
4105         */
4106        public boolean isDomainCrosshairVisible() {
4107            return this.domainCrosshairVisible;
4108        }
4109    
4110        /**
4111         * Sets the flag indicating whether or not the domain crosshair is visible
4112         * and, if the flag changes, sends a {@link PlotChangeEvent} to all
4113         * registered listeners.
4114         *
4115         * @param flag  the new value of the flag.
4116         *
4117         * @see #isDomainCrosshairVisible()
4118         */
4119        public void setDomainCrosshairVisible(boolean flag) {
4120            if (this.domainCrosshairVisible != flag) {
4121                this.domainCrosshairVisible = flag;
4122                fireChangeEvent();
4123            }
4124        }
4125    
4126        /**
4127         * Returns a flag indicating whether or not the crosshair should "lock-on"
4128         * to actual data values.
4129         *
4130         * @return The flag.
4131         *
4132         * @see #setDomainCrosshairLockedOnData(boolean)
4133         */
4134        public boolean isDomainCrosshairLockedOnData() {
4135            return this.domainCrosshairLockedOnData;
4136        }
4137    
4138        /**
4139         * Sets the flag indicating whether or not the domain crosshair should
4140         * "lock-on" to actual data values.  If the flag value changes, this
4141         * method sends a {@link PlotChangeEvent} to all registered listeners.
4142         *
4143         * @param flag  the flag.
4144         *
4145         * @see #isDomainCrosshairLockedOnData()
4146         */
4147        public void setDomainCrosshairLockedOnData(boolean flag) {
4148            if (this.domainCrosshairLockedOnData != flag) {
4149                this.domainCrosshairLockedOnData = flag;
4150                fireChangeEvent();
4151            }
4152        }
4153    
4154        /**
4155         * Returns the domain crosshair value.
4156         *
4157         * @return The value.
4158         *
4159         * @see #setDomainCrosshairValue(double)
4160         */
4161        public double getDomainCrosshairValue() {
4162            return this.domainCrosshairValue;
4163        }
4164    
4165        /**
4166         * Sets the domain crosshair value and sends a {@link PlotChangeEvent} to
4167         * all registered listeners (provided that the domain crosshair is visible).
4168         *
4169         * @param value  the value.
4170         *
4171         * @see #getDomainCrosshairValue()
4172         */
4173        public void setDomainCrosshairValue(double value) {
4174            setDomainCrosshairValue(value, true);
4175        }
4176    
4177        /**
4178         * Sets the domain crosshair value and, if requested, sends a
4179         * {@link PlotChangeEvent} to all registered listeners (provided that the
4180         * domain crosshair is visible).
4181         *
4182         * @param value  the new value.
4183         * @param notify  notify listeners?
4184         *
4185         * @see #getDomainCrosshairValue()
4186         */
4187        public void setDomainCrosshairValue(double value, boolean notify) {
4188            this.domainCrosshairValue = value;
4189            if (isDomainCrosshairVisible() && notify) {
4190                fireChangeEvent();
4191            }
4192        }
4193    
4194        /**
4195         * Returns the {@link Stroke} used to draw the crosshair (if visible).
4196         *
4197         * @return The crosshair stroke (never <code>null</code>).
4198         *
4199         * @see #setDomainCrosshairStroke(Stroke)
4200         * @see #isDomainCrosshairVisible()
4201         * @see #getDomainCrosshairPaint()
4202         */
4203        public Stroke getDomainCrosshairStroke() {
4204            return this.domainCrosshairStroke;
4205        }
4206    
4207        /**
4208         * Sets the Stroke used to draw the crosshairs (if visible) and notifies
4209         * registered listeners that the axis has been modified.
4210         *
4211         * @param stroke  the new crosshair stroke (<code>null</code> not
4212         *     permitted).
4213         *
4214         * @see #getDomainCrosshairStroke()
4215         */
4216        public void setDomainCrosshairStroke(Stroke stroke) {
4217            if (stroke == null) {
4218                throw new IllegalArgumentException("Null 'stroke' argument.");
4219            }
4220            this.domainCrosshairStroke = stroke;
4221            fireChangeEvent();
4222        }
4223    
4224        /**
4225         * Returns the domain crosshair paint.
4226         *
4227         * @return The crosshair paint (never <code>null</code>).
4228         *
4229         * @see #setDomainCrosshairPaint(Paint)
4230         * @see #isDomainCrosshairVisible()
4231         * @see #getDomainCrosshairStroke()
4232         */
4233        public Paint getDomainCrosshairPaint() {
4234            return this.domainCrosshairPaint;
4235        }
4236    
4237        /**
4238         * Sets the paint used to draw the crosshairs (if visible) and sends a
4239         * {@link PlotChangeEvent} to all registered listeners.
4240         *
4241         * @param paint the new crosshair paint (<code>null</code> not permitted).
4242         *
4243         * @see #getDomainCrosshairPaint()
4244         */
4245        public void setDomainCrosshairPaint(Paint paint) {
4246            if (paint == null) {
4247                throw new IllegalArgumentException("Null 'paint' argument.");
4248            }
4249            this.domainCrosshairPaint = paint;
4250            fireChangeEvent();
4251        }
4252    
4253        /**
4254         * Returns a flag indicating whether or not the range crosshair is visible.
4255         *
4256         * @return The flag.
4257         *
4258         * @see #setRangeCrosshairVisible(boolean)
4259         * @see #isDomainCrosshairVisible()
4260         */
4261        public boolean isRangeCrosshairVisible() {
4262            return this.rangeCrosshairVisible;
4263        }
4264    
4265        /**
4266         * Sets the flag indicating whether or not the range crosshair is visible.
4267         * If the flag value changes, this method sends a {@link PlotChangeEvent}
4268         * to all registered listeners.
4269         *
4270         * @param flag  the new value of the flag.
4271         *
4272         * @see #isRangeCrosshairVisible()
4273         */
4274        public void setRangeCrosshairVisible(boolean flag) {
4275            if (this.rangeCrosshairVisible != flag) {
4276                this.rangeCrosshairVisible = flag;
4277                fireChangeEvent();
4278            }
4279        }
4280    
4281        /**
4282         * Returns a flag indicating whether or not the crosshair should "lock-on"
4283         * to actual data values.
4284         *
4285         * @return The flag.
4286         *
4287         * @see #setRangeCrosshairLockedOnData(boolean)
4288         */
4289        public boolean isRangeCrosshairLockedOnData() {
4290            return this.rangeCrosshairLockedOnData;
4291        }
4292    
4293        /**
4294         * Sets the flag indicating whether or not the range crosshair should
4295         * "lock-on" to actual data values.  If the flag value changes, this method
4296         * sends a {@link PlotChangeEvent} to all registered listeners.
4297         *
4298         * @param flag  the flag.
4299         *
4300         * @see #isRangeCrosshairLockedOnData()
4301         */
4302        public void setRangeCrosshairLockedOnData(boolean flag) {
4303            if (this.rangeCrosshairLockedOnData != flag) {
4304                this.rangeCrosshairLockedOnData = flag;
4305                fireChangeEvent();
4306            }
4307        }
4308    
4309        /**
4310         * Returns the range crosshair value.
4311         *
4312         * @return The value.
4313         *
4314         * @see #setRangeCrosshairValue(double)
4315         */
4316        public double getRangeCrosshairValue() {
4317            return this.rangeCrosshairValue;
4318        }
4319    
4320        /**
4321         * Sets the range crosshair value.
4322         * <P>
4323         * Registered listeners are notified that the plot has been modified, but
4324         * only if the crosshair is visible.
4325         *
4326         * @param value  the new value.
4327         *
4328         * @see #getRangeCrosshairValue()
4329         */
4330        public void setRangeCrosshairValue(double value) {
4331            setRangeCrosshairValue(value, true);
4332        }
4333    
4334        /**
4335         * Sets the range crosshair value and sends a {@link PlotChangeEvent} to
4336         * all registered listeners, but only if the crosshair is visible.
4337         *
4338         * @param value  the new value.
4339         * @param notify  a flag that controls whether or not listeners are
4340         *                notified.
4341         *
4342         * @see #getRangeCrosshairValue()
4343         */
4344        public void setRangeCrosshairValue(double value, boolean notify) {
4345            this.rangeCrosshairValue = value;
4346            if (isRangeCrosshairVisible() && notify) {
4347                fireChangeEvent();
4348            }
4349        }
4350    
4351        /**
4352         * Returns the stroke used to draw the crosshair (if visible).
4353         *
4354         * @return The crosshair stroke (never <code>null</code>).
4355         *
4356         * @see #setRangeCrosshairStroke(Stroke)
4357         * @see #isRangeCrosshairVisible()
4358         * @see #getRangeCrosshairPaint()
4359         */
4360        public Stroke getRangeCrosshairStroke() {
4361            return this.rangeCrosshairStroke;
4362        }
4363    
4364        /**
4365         * Sets the stroke used to draw the crosshairs (if visible) and sends a
4366         * {@link PlotChangeEvent} to all registered listeners.
4367         *
4368         * @param stroke  the new crosshair stroke (<code>null</code> not
4369         *         permitted).
4370         *
4371         * @see #getRangeCrosshairStroke()
4372         */
4373        public void setRangeCrosshairStroke(Stroke stroke) {
4374            if (stroke == null) {
4375                throw new IllegalArgumentException("Null 'stroke' argument.");
4376            }
4377            this.rangeCrosshairStroke = stroke;
4378            fireChangeEvent();
4379        }
4380    
4381        /**
4382         * Returns the range crosshair paint.
4383         *
4384         * @return The crosshair paint (never <code>null</code>).
4385         *
4386         * @see #setRangeCrosshairPaint(Paint)
4387         * @see #isRangeCrosshairVisible()
4388         * @see #getRangeCrosshairStroke()
4389         */
4390        public Paint getRangeCrosshairPaint() {
4391            return this.rangeCrosshairPaint;
4392        }
4393    
4394        /**
4395         * Sets the paint used to color the crosshairs (if visible) and sends a
4396         * {@link PlotChangeEvent} to all registered listeners.
4397         *
4398         * @param paint the new crosshair paint (<code>null</code> not permitted).
4399         *
4400         * @see #getRangeCrosshairPaint()
4401         */
4402        public void setRangeCrosshairPaint(Paint paint) {
4403            if (paint == null) {
4404                throw new IllegalArgumentException("Null 'paint' argument.");
4405            }
4406            this.rangeCrosshairPaint = paint;
4407            fireChangeEvent();
4408        }
4409    
4410        /**
4411         * Returns the fixed domain axis space.
4412         *
4413         * @return The fixed domain axis space (possibly <code>null</code>).
4414         *
4415         * @see #setFixedDomainAxisSpace(AxisSpace)
4416         */
4417        public AxisSpace getFixedDomainAxisSpace() {
4418            return this.fixedDomainAxisSpace;
4419        }
4420    
4421        /**
4422         * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
4423         * all registered listeners.
4424         *
4425         * @param space  the space (<code>null</code> permitted).
4426         *
4427         * @see #getFixedDomainAxisSpace()
4428         */
4429        public void setFixedDomainAxisSpace(AxisSpace space) {
4430            setFixedDomainAxisSpace(space, true);
4431        }
4432    
4433        /**
4434         * Sets the fixed domain axis space and, if requested, sends a
4435         * {@link PlotChangeEvent} to all registered listeners.
4436         *
4437         * @param space  the space (<code>null</code> permitted).
4438         * @param notify  notify listeners?
4439         *
4440         * @see #getFixedDomainAxisSpace()
4441         *
4442         * @since 1.0.9
4443         */
4444        public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) {
4445            this.fixedDomainAxisSpace = space;
4446            if (notify) {
4447                fireChangeEvent();
4448            }
4449        }
4450    
4451        /**
4452         * Returns the fixed range axis space.
4453         *
4454         * @return The fixed range axis space (possibly <code>null</code>).
4455         *
4456         * @see #setFixedRangeAxisSpace(AxisSpace)
4457         */
4458        public AxisSpace getFixedRangeAxisSpace() {
4459            return this.fixedRangeAxisSpace;
4460        }
4461    
4462        /**
4463         * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
4464         * all registered listeners.
4465         *
4466         * @param space  the space (<code>null</code> permitted).
4467         *
4468         * @see #getFixedRangeAxisSpace()
4469         */
4470        public void setFixedRangeAxisSpace(AxisSpace space) {
4471            setFixedRangeAxisSpace(space, true);
4472        }
4473    
4474        /**
4475         * Sets the fixed range axis space and, if requested, sends a
4476         * {@link PlotChangeEvent} to all registered listeners.
4477         *
4478         * @param space  the space (<code>null</code> permitted).
4479         * @param notify  notify listeners?
4480         *
4481         * @see #getFixedRangeAxisSpace()
4482         *
4483         * @since 1.0.9
4484         */
4485        public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) {
4486            this.fixedRangeAxisSpace = space;
4487            if (notify) {
4488                fireChangeEvent();
4489            }
4490        }
4491    
4492        /**
4493         * Multiplies the range on the domain axis/axes by the specified factor.
4494         *
4495         * @param factor  the zoom factor.
4496         * @param info  the plot rendering info.
4497         * @param source  the source point (in Java2D space).
4498         *
4499         * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D)
4500         */
4501        public void zoomDomainAxes(double factor, PlotRenderingInfo info,
4502                                   Point2D source) {
4503            // delegate to other method
4504            zoomDomainAxes(factor, info, source, false);
4505        }
4506    
4507        /**
4508         * Multiplies the range on the domain axis/axes by the specified factor.
4509         *
4510         * @param factor  the zoom factor.
4511         * @param info  the plot rendering info.
4512         * @param source  the source point (in Java2D space).
4513         * @param useAnchor  use source point as zoom anchor?
4514         *
4515         * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean)
4516         *
4517         * @since 1.0.7
4518         */
4519        public void zoomDomainAxes(double factor, PlotRenderingInfo info,
4520                                   Point2D source, boolean useAnchor) {
4521    
4522            // perform the zoom on each domain axis
4523            for (int i = 0; i < this.domainAxes.size(); i++) {
4524                ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
4525                if (domainAxis != null) {
4526                    if (useAnchor) {
4527                        // get the relevant source coordinate given the plot
4528                        // orientation
4529                        double sourceX = source.getX();
4530                        if (this.orientation == PlotOrientation.HORIZONTAL) {
4531                            sourceX = source.getY();
4532                        }
4533                        double anchorX = domainAxis.java2DToValue(sourceX,
4534                                info.getDataArea(), getDomainAxisEdge());
4535                        domainAxis.resizeRange(factor, anchorX);
4536                    }
4537                    else {
4538                        domainAxis.resizeRange(factor);
4539                    }
4540                }
4541            }
4542        }
4543    
4544        /**
4545         * Zooms in on the domain axis/axes.  The new lower and upper bounds are
4546         * specified as percentages of the current axis range, where 0 percent is
4547         * the current lower bound and 100 percent is the current upper bound.
4548         *
4549         * @param lowerPercent  a percentage that determines the new lower bound
4550         *                      for the axis (e.g. 0.20 is twenty percent).
4551         * @param upperPercent  a percentage that determines the new upper bound
4552         *                      for the axis (e.g. 0.80 is eighty percent).
4553         * @param info  the plot rendering info.
4554         * @param source  the source point (ignored).
4555         *
4556         * @see #zoomRangeAxes(double, double, PlotRenderingInfo, Point2D)
4557         */
4558        public void zoomDomainAxes(double lowerPercent, double upperPercent,
4559                                   PlotRenderingInfo info, Point2D source) {
4560            for (int i = 0; i < this.domainAxes.size(); i++) {
4561                ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
4562                if (domainAxis != null) {
4563                    domainAxis.zoomRange(lowerPercent, upperPercent);
4564                }
4565            }
4566        }
4567    
4568        /**
4569         * Multiplies the range on the range axis/axes by the specified factor.
4570         *
4571         * @param factor  the zoom factor.
4572         * @param info  the plot rendering info.
4573         * @param source  the source point.
4574         *
4575         * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
4576         */
4577        public void zoomRangeAxes(double factor, PlotRenderingInfo info,
4578                                  Point2D source) {
4579            // delegate to other method
4580            zoomRangeAxes(factor, info, source, false);
4581        }
4582    
4583        /**
4584         * Multiplies the range on the range axis/axes by the specified factor.
4585         *
4586         * @param factor  the zoom factor.
4587         * @param info  the plot rendering info.
4588         * @param source  the source point.
4589         * @param useAnchor  a flag that controls whether or not the source point
4590         *         is used for the zoom anchor.
4591         *
4592         * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
4593         *
4594         * @since 1.0.7
4595         */
4596        public void zoomRangeAxes(double factor, PlotRenderingInfo info,
4597                                  Point2D source, boolean useAnchor) {
4598    
4599            // perform the zoom on each range axis
4600            for (int i = 0; i < this.rangeAxes.size(); i++) {
4601                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
4602                if (rangeAxis != null) {
4603                    if (useAnchor) {
4604                        // get the relevant source coordinate given the plot
4605                        // orientation
4606                        double sourceY = source.getY();
4607                        if (this.orientation == PlotOrientation.HORIZONTAL) {
4608                            sourceY = source.getX();
4609                        }
4610                        double anchorY = rangeAxis.java2DToValue(sourceY,
4611                                info.getDataArea(), getRangeAxisEdge());
4612                        rangeAxis.resizeRange(factor, anchorY);
4613                    }
4614                    else {
4615                        rangeAxis.resizeRange(factor);
4616                    }
4617                }
4618            }
4619        }
4620    
4621        /**
4622         * Zooms in on the range axes.
4623         *
4624         * @param lowerPercent  the lower bound.
4625         * @param upperPercent  the upper bound.
4626         * @param info  the plot rendering info.
4627         * @param source  the source point.
4628         *
4629         * @see #zoomDomainAxes(double, double, PlotRenderingInfo, Point2D)
4630         */
4631        public void zoomRangeAxes(double lowerPercent, double upperPercent,
4632                                  PlotRenderingInfo info, Point2D source) {
4633            for (int i = 0; i < this.rangeAxes.size(); i++) {
4634                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
4635                if (rangeAxis != null) {
4636                    rangeAxis.zoomRange(lowerPercent, upperPercent);
4637                }
4638            }
4639        }
4640    
4641        /**
4642         * Returns <code>true</code>, indicating that the domain axis/axes for this
4643         * plot are zoomable.
4644         *
4645         * @return A boolean.
4646         *
4647         * @see #isRangeZoomable()
4648         */
4649        public boolean isDomainZoomable() {
4650            return true;
4651        }
4652    
4653        /**
4654         * Returns <code>true</code>, indicating that the range axis/axes for this
4655         * plot are zoomable.
4656         *
4657         * @return A boolean.
4658         *
4659         * @see #isDomainZoomable()
4660         */
4661        public boolean isRangeZoomable() {
4662            return true;
4663        }
4664    
4665        /**
4666         * Returns the number of series in the primary dataset for this plot.  If
4667         * the dataset is <code>null</code>, the method returns 0.
4668         *
4669         * @return The series count.
4670         */
4671        public int getSeriesCount() {
4672            int result = 0;
4673            XYDataset dataset = getDataset();
4674            if (dataset != null) {
4675                result = dataset.getSeriesCount();
4676            }
4677            return result;
4678        }
4679    
4680        /**
4681         * Returns the fixed legend items, if any.
4682         *
4683         * @return The legend items (possibly <code>null</code>).
4684         *
4685         * @see #setFixedLegendItems(LegendItemCollection)
4686         */
4687        public LegendItemCollection getFixedLegendItems() {
4688            return this.fixedLegendItems;
4689        }
4690    
4691        /**
4692         * Sets the fixed legend items for the plot.  Leave this set to
4693         * <code>null</code> if you prefer the legend items to be created
4694         * automatically.
4695         *
4696         * @param items  the legend items (<code>null</code> permitted).
4697         *
4698         * @see #getFixedLegendItems()
4699         */
4700        public void setFixedLegendItems(LegendItemCollection items) {
4701            this.fixedLegendItems = items;
4702            fireChangeEvent();
4703        }
4704    
4705        /**
4706         * Returns the legend items for the plot.  Each legend item is generated by
4707         * the plot's renderer, since the renderer is responsible for the visual
4708         * representation of the data.
4709         *
4710         * @return The legend items.
4711         */
4712        public LegendItemCollection getLegendItems() {
4713            if (this.fixedLegendItems != null) {
4714                return this.fixedLegendItems;
4715            }
4716            LegendItemCollection result = new LegendItemCollection();
4717            int count = this.datasets.size();
4718            for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
4719                XYDataset dataset = getDataset(datasetIndex);
4720                if (dataset != null) {
4721                    XYItemRenderer renderer = getRenderer(datasetIndex);
4722                    if (renderer == null) {
4723                        renderer = getRenderer(0);
4724                    }
4725                    if (renderer != null) {
4726                        int seriesCount = dataset.getSeriesCount();
4727                        for (int i = 0; i < seriesCount; i++) {
4728                            if (renderer.isSeriesVisible(i)
4729                                    && renderer.isSeriesVisibleInLegend(i)) {
4730                                LegendItem item = renderer.getLegendItem(
4731                                        datasetIndex, i);
4732                                if (item != null) {
4733                                    result.add(item);
4734                                }
4735                            }
4736                        }
4737                    }
4738                }
4739            }
4740            return result;
4741        }
4742    
4743        /**
4744         * Tests this plot for equality with another object.
4745         *
4746         * @param obj  the object (<code>null</code> permitted).
4747         *
4748         * @return <code>true</code> or <code>false</code>.
4749         */
4750        public boolean equals(Object obj) {
4751    
4752            if (obj == this) {
4753                return true;
4754            }
4755            if (!(obj instanceof XYPlot)) {
4756                return false;
4757            }
4758    
4759            XYPlot that = (XYPlot) obj;
4760            if (this.weight != that.weight) {
4761                return false;
4762            }
4763            if (this.orientation != that.orientation) {
4764                return false;
4765            }
4766            if (!this.domainAxes.equals(that.domainAxes)) {
4767                return false;
4768            }
4769            if (!this.domainAxisLocations.equals(that.domainAxisLocations)) {
4770                return false;
4771            }
4772            if (this.rangeCrosshairLockedOnData
4773                    != that.rangeCrosshairLockedOnData) {
4774                return false;
4775            }
4776            if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
4777                return false;
4778            }
4779            if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) {
4780                return false;
4781            }
4782            if (this.domainZeroBaselineVisible != that.domainZeroBaselineVisible) {
4783                return false;
4784            }
4785            if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) {
4786                return false;
4787            }
4788            if (this.domainCrosshairVisible != that.domainCrosshairVisible) {
4789                return false;
4790            }
4791            if (this.domainCrosshairValue != that.domainCrosshairValue) {
4792                return false;
4793            }
4794            if (this.domainCrosshairLockedOnData
4795                    != that.domainCrosshairLockedOnData) {
4796                return false;
4797            }
4798            if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) {
4799                return false;
4800            }
4801            if (this.rangeCrosshairValue != that.rangeCrosshairValue) {
4802                return false;
4803            }
4804            if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) {
4805                return false;
4806            }
4807            if (!ObjectUtilities.equal(this.renderers, that.renderers)) {
4808                return false;
4809            }
4810            if (!ObjectUtilities.equal(this.rangeAxes, that.rangeAxes)) {
4811                return false;
4812            }
4813            if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) {
4814                return false;
4815            }
4816            if (!ObjectUtilities.equal(this.datasetToDomainAxisMap,
4817                    that.datasetToDomainAxisMap)) {
4818                return false;
4819            }
4820            if (!ObjectUtilities.equal(this.datasetToRangeAxisMap,
4821                    that.datasetToRangeAxisMap)) {
4822                return false;
4823            }
4824            if (!ObjectUtilities.equal(this.domainGridlineStroke,
4825                    that.domainGridlineStroke)) {
4826                return false;
4827            }
4828            if (!PaintUtilities.equal(this.domainGridlinePaint,
4829                    that.domainGridlinePaint)) {
4830                return false;
4831            }
4832            if (!ObjectUtilities.equal(this.rangeGridlineStroke,
4833                    that.rangeGridlineStroke)) {
4834                return false;
4835            }
4836            if (!PaintUtilities.equal(this.rangeGridlinePaint,
4837                    that.rangeGridlinePaint)) {
4838                return false;
4839            }
4840            if (!PaintUtilities.equal(this.domainZeroBaselinePaint,
4841                    that.domainZeroBaselinePaint)) {
4842                return false;
4843            }
4844            if (!ObjectUtilities.equal(this.domainZeroBaselineStroke,
4845                    that.domainZeroBaselineStroke)) {
4846                return false;
4847            }
4848            if (!PaintUtilities.equal(this.rangeZeroBaselinePaint,
4849                    that.rangeZeroBaselinePaint)) {
4850                return false;
4851            }
4852            if (!ObjectUtilities.equal(this.rangeZeroBaselineStroke,
4853                    that.rangeZeroBaselineStroke)) {
4854                return false;
4855            }
4856            if (!ObjectUtilities.equal(this.domainCrosshairStroke,
4857                    that.domainCrosshairStroke)) {
4858                return false;
4859            }
4860            if (!PaintUtilities.equal(this.domainCrosshairPaint,
4861                    that.domainCrosshairPaint)) {
4862                return false;
4863            }
4864            if (!ObjectUtilities.equal(this.rangeCrosshairStroke,
4865                    that.rangeCrosshairStroke)) {
4866                return false;
4867            }
4868            if (!PaintUtilities.equal(this.rangeCrosshairPaint,
4869                    that.rangeCrosshairPaint)) {
4870                return false;
4871            }
4872            if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
4873                    that.foregroundDomainMarkers)) {
4874                return false;
4875            }
4876            if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
4877                    that.backgroundDomainMarkers)) {
4878                return false;
4879            }
4880            if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
4881                    that.foregroundRangeMarkers)) {
4882                return false;
4883            }
4884            if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
4885                    that.backgroundRangeMarkers)) {
4886                return false;
4887            }
4888            if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
4889                    that.foregroundDomainMarkers)) {
4890                return false;
4891            }
4892            if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
4893                    that.backgroundDomainMarkers)) {
4894                return false;
4895            }
4896            if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
4897                    that.foregroundRangeMarkers)) {
4898                return false;
4899            }
4900            if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
4901                    that.backgroundRangeMarkers)) {
4902                return false;
4903            }
4904            if (!ObjectUtilities.equal(this.annotations, that.annotations)) {
4905                return false;
4906            }
4907            if (!PaintUtilities.equal(this.domainTickBandPaint,
4908                    that.domainTickBandPaint)) {
4909                return false;
4910            }
4911            if (!PaintUtilities.equal(this.rangeTickBandPaint,
4912                    that.rangeTickBandPaint)) {
4913                return false;
4914            }
4915            if (!this.quadrantOrigin.equals(that.quadrantOrigin)) {
4916                return false;
4917            }
4918            for (int i = 0; i < 4; i++) {
4919                if (!PaintUtilities.equal(this.quadrantPaint[i],
4920                        that.quadrantPaint[i])) {
4921                    return false;
4922                }
4923            }
4924            return super.equals(obj);
4925        }
4926    
4927        /**
4928         * Returns a clone of the plot.
4929         *
4930         * @return A clone.
4931         *
4932         * @throws CloneNotSupportedException  this can occur if some component of
4933         *         the plot cannot be cloned.
4934         */
4935        public Object clone() throws CloneNotSupportedException {
4936    
4937            XYPlot clone = (XYPlot) super.clone();
4938            clone.domainAxes = (ObjectList) ObjectUtilities.clone(this.domainAxes);
4939            for (int i = 0; i < this.domainAxes.size(); i++) {
4940                ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
4941                if (axis != null) {
4942                    ValueAxis clonedAxis = (ValueAxis) axis.clone();
4943                    clone.domainAxes.set(i, clonedAxis);
4944                    clonedAxis.setPlot(clone);
4945                    clonedAxis.addChangeListener(clone);
4946                }
4947            }
4948            clone.domainAxisLocations = (ObjectList)
4949                    this.domainAxisLocations.clone();
4950    
4951            clone.rangeAxes = (ObjectList) ObjectUtilities.clone(this.rangeAxes);
4952            for (int i = 0; i < this.rangeAxes.size(); i++) {
4953                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
4954                if (axis != null) {
4955                    ValueAxis clonedAxis = (ValueAxis) axis.clone();
4956                    clone.rangeAxes.set(i, clonedAxis);
4957                    clonedAxis.setPlot(clone);
4958                    clonedAxis.addChangeListener(clone);
4959                }
4960            }
4961            clone.rangeAxisLocations = (ObjectList) ObjectUtilities.clone(
4962                    this.rangeAxisLocations);
4963    
4964            // the datasets are not cloned, but listeners need to be added...
4965            clone.datasets = (ObjectList) ObjectUtilities.clone(this.datasets);
4966            for (int i = 0; i < clone.datasets.size(); ++i) {
4967                XYDataset d = getDataset(i);
4968                if (d != null) {
4969                    d.addChangeListener(clone);
4970                }
4971            }
4972    
4973            clone.datasetToDomainAxisMap = new TreeMap();
4974            clone.datasetToDomainAxisMap.putAll(this.datasetToDomainAxisMap);
4975            clone.datasetToRangeAxisMap = new TreeMap();
4976            clone.datasetToRangeAxisMap.putAll(this.datasetToRangeAxisMap);
4977    
4978            clone.renderers = (ObjectList) ObjectUtilities.clone(this.renderers);
4979            for (int i = 0; i < this.renderers.size(); i++) {
4980                XYItemRenderer renderer2 = (XYItemRenderer) this.renderers.get(i);
4981                if (renderer2 instanceof PublicCloneable) {
4982                    PublicCloneable pc = (PublicCloneable) renderer2;
4983                    clone.renderers.set(i, pc.clone());
4984                }
4985            }
4986            clone.foregroundDomainMarkers = (Map) ObjectUtilities.clone(
4987                    this.foregroundDomainMarkers);
4988            clone.backgroundDomainMarkers = (Map) ObjectUtilities.clone(
4989                    this.backgroundDomainMarkers);
4990            clone.foregroundRangeMarkers = (Map) ObjectUtilities.clone(
4991                    this.foregroundRangeMarkers);
4992            clone.backgroundRangeMarkers = (Map) ObjectUtilities.clone(
4993                    this.backgroundRangeMarkers);
4994            clone.annotations = (List) ObjectUtilities.deepClone(this.annotations);
4995            if (this.fixedDomainAxisSpace != null) {
4996                clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtilities.clone(
4997                        this.fixedDomainAxisSpace);
4998            }
4999            if (this.fixedRangeAxisSpace != null) {
5000                clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtilities.clone(
5001                        this.fixedRangeAxisSpace);
5002            }
5003    
5004            clone.quadrantOrigin = (Point2D) ObjectUtilities.clone(
5005                    this.quadrantOrigin);
5006            clone.quadrantPaint = (Paint[]) this.quadrantPaint.clone();
5007            return clone;
5008    
5009        }
5010    
5011        /**
5012         * Provides serialization support.
5013         *
5014         * @param stream  the output stream.
5015         *
5016         * @throws IOException  if there is an I/O error.
5017         */
5018        private void writeObject(ObjectOutputStream stream) throws IOException {
5019            stream.defaultWriteObject();
5020            SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
5021            SerialUtilities.writePaint(this.domainGridlinePaint, stream);
5022            SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
5023            SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
5024            SerialUtilities.writeStroke(this.rangeZeroBaselineStroke, stream);
5025            SerialUtilities.writePaint(this.rangeZeroBaselinePaint, stream);
5026            SerialUtilities.writeStroke(this.domainCrosshairStroke, stream);
5027            SerialUtilities.writePaint(this.domainCrosshairPaint, stream);
5028            SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream);
5029            SerialUtilities.writePaint(this.rangeCrosshairPaint, stream);
5030            SerialUtilities.writePaint(this.domainTickBandPaint, stream);
5031            SerialUtilities.writePaint(this.rangeTickBandPaint, stream);
5032            SerialUtilities.writePoint2D(this.quadrantOrigin, stream);
5033            for (int i = 0; i < 4; i++) {
5034                SerialUtilities.writePaint(this.quadrantPaint[i], stream);
5035            }
5036            SerialUtilities.writeStroke(this.domainZeroBaselineStroke, stream);
5037            SerialUtilities.writePaint(this.domainZeroBaselinePaint, stream);
5038        }
5039    
5040        /**
5041         * Provides serialization support.
5042         *
5043         * @param stream  the input stream.
5044         *
5045         * @throws IOException  if there is an I/O error.
5046         * @throws ClassNotFoundException  if there is a classpath problem.
5047         */
5048        private void readObject(ObjectInputStream stream)
5049            throws IOException, ClassNotFoundException {
5050    
5051            stream.defaultReadObject();
5052            this.domainGridlineStroke = SerialUtilities.readStroke(stream);
5053            this.domainGridlinePaint = SerialUtilities.readPaint(stream);
5054            this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
5055            this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
5056            this.rangeZeroBaselineStroke = SerialUtilities.readStroke(stream);
5057            this.rangeZeroBaselinePaint = SerialUtilities.readPaint(stream);
5058            this.domainCrosshairStroke = SerialUtilities.readStroke(stream);
5059            this.domainCrosshairPaint = SerialUtilities.readPaint(stream);
5060            this.rangeCrosshairStroke = SerialUtilities.readStroke(stream);
5061            this.rangeCrosshairPaint = SerialUtilities.readPaint(stream);
5062            this.domainTickBandPaint = SerialUtilities.readPaint(stream);
5063            this.rangeTickBandPaint = SerialUtilities.readPaint(stream);
5064            this.quadrantOrigin = SerialUtilities.readPoint2D(stream);
5065            this.quadrantPaint = new Paint[4];
5066            for (int i = 0; i < 4; i++) {
5067                this.quadrantPaint[i] = SerialUtilities.readPaint(stream);
5068            }
5069    
5070            this.domainZeroBaselineStroke = SerialUtilities.readStroke(stream);
5071            this.domainZeroBaselinePaint = SerialUtilities.readPaint(stream);
5072    
5073            // register the plot as a listener with its axes, datasets, and
5074            // renderers...
5075            int domainAxisCount = this.domainAxes.size();
5076            for (int i = 0; i < domainAxisCount; i++) {
5077                Axis axis = (Axis) this.domainAxes.get(i);
5078                if (axis != null) {
5079                    axis.setPlot(this);
5080                    axis.addChangeListener(this);
5081                }
5082            }
5083            int rangeAxisCount = this.rangeAxes.size();
5084            for (int i = 0; i < rangeAxisCount; i++) {
5085                Axis axis = (Axis) this.rangeAxes.get(i);
5086                if (axis != null) {
5087                    axis.setPlot(this);
5088                    axis.addChangeListener(this);
5089                }
5090            }
5091            int datasetCount = this.datasets.size();
5092            for (int i = 0; i < datasetCount; i++) {
5093                Dataset dataset = (Dataset) this.datasets.get(i);
5094                if (dataset != null) {
5095                    dataset.addChangeListener(this);
5096                }
5097            }
5098            int rendererCount = this.renderers.size();
5099            for (int i = 0; i < rendererCount; i++) {
5100                XYItemRenderer renderer = (XYItemRenderer) this.renderers.get(i);
5101                if (renderer != null) {
5102                    renderer.addChangeListener(this);
5103                }
5104            }
5105    
5106        }
5107    
5108    }