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