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