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