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