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