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