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