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.17 2007/06/07 12:49:36 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     * 07-Jun-2007 : Override drawBackground() for new GradientPaint handling (DG);
151     * 
152     */
153    
154    package org.jfree.chart.plot;
155    
156    import java.awt.AlphaComposite;
157    import java.awt.BasicStroke;
158    import java.awt.Color;
159    import java.awt.Composite;
160    import java.awt.Font;
161    import java.awt.Graphics2D;
162    import java.awt.Paint;
163    import java.awt.Shape;
164    import java.awt.Stroke;
165    import java.awt.geom.Line2D;
166    import java.awt.geom.Point2D;
167    import java.awt.geom.Rectangle2D;
168    import java.io.IOException;
169    import java.io.ObjectInputStream;
170    import java.io.ObjectOutputStream;
171    import java.io.Serializable;
172    import java.util.ArrayList;
173    import java.util.Collection;
174    import java.util.Collections;
175    import java.util.HashMap;
176    import java.util.Iterator;
177    import java.util.List;
178    import java.util.Map;
179    import java.util.ResourceBundle;
180    import java.util.Set;
181    
182    import org.jfree.chart.LegendItem;
183    import org.jfree.chart.LegendItemCollection;
184    import org.jfree.chart.annotations.CategoryAnnotation;
185    import org.jfree.chart.axis.Axis;
186    import org.jfree.chart.axis.AxisCollection;
187    import org.jfree.chart.axis.AxisLocation;
188    import org.jfree.chart.axis.AxisSpace;
189    import org.jfree.chart.axis.AxisState;
190    import org.jfree.chart.axis.CategoryAnchor;
191    import org.jfree.chart.axis.CategoryAxis;
192    import org.jfree.chart.axis.ValueAxis;
193    import org.jfree.chart.axis.ValueTick;
194    import org.jfree.chart.event.ChartChangeEventType;
195    import org.jfree.chart.event.PlotChangeEvent;
196    import org.jfree.chart.event.RendererChangeEvent;
197    import org.jfree.chart.event.RendererChangeListener;
198    import org.jfree.chart.renderer.category.CategoryItemRenderer;
199    import org.jfree.chart.renderer.category.CategoryItemRendererState;
200    import org.jfree.data.Range;
201    import org.jfree.data.category.CategoryDataset;
202    import org.jfree.data.general.Dataset;
203    import org.jfree.data.general.DatasetChangeEvent;
204    import org.jfree.data.general.DatasetUtilities;
205    import org.jfree.io.SerialUtilities;
206    import org.jfree.ui.Layer;
207    import org.jfree.ui.RectangleEdge;
208    import org.jfree.ui.RectangleInsets;
209    import org.jfree.util.ObjectList;
210    import org.jfree.util.ObjectUtilities;
211    import org.jfree.util.PaintUtilities;
212    import org.jfree.util.PublicCloneable;
213    import org.jfree.util.SortOrder;
214    
215    /**
216     * A general plotting class that uses data from a {@link CategoryDataset} and 
217     * renders each data item using a {@link CategoryItemRenderer}.
218     */
219    public class CategoryPlot extends Plot 
220                              implements ValueAxisPlot, 
221                                         Zoomable,
222                                         RendererChangeListener,
223                                         Cloneable, PublicCloneable, Serializable {
224    
225        /** For serialization. */
226        private static final long serialVersionUID = -3537691700434728188L;
227        
228        /** 
229         * The default visibility of the grid lines plotted against the domain 
230         * axis. 
231         */
232        public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false;
233    
234        /** 
235         * The default visibility of the grid lines plotted against the range 
236         * axis. 
237         */
238        public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true;
239    
240        /** The default grid line stroke. */
241        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
242            BasicStroke.CAP_BUTT,
243            BasicStroke.JOIN_BEVEL,
244            0.0f,
245            new float[] {2.0f, 2.0f},
246            0.0f);
247    
248        /** The default grid line paint. */
249        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
250    
251        /** The default value label font. */
252        public static final Font DEFAULT_VALUE_LABEL_FONT 
253                = new Font("SansSerif", Font.PLAIN, 10);
254    
255        /** 
256         * The default crosshair visibility. 
257         * 
258         * @since 1.0.5
259         */
260        public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
261    
262        /** 
263         * The default crosshair stroke. 
264         * 
265         * @since 1.0.5
266         */
267        public static final Stroke DEFAULT_CROSSHAIR_STROKE
268                = DEFAULT_GRIDLINE_STROKE;
269    
270        /** 
271         * The default crosshair paint. 
272         * 
273         * @since 1.0.5
274         */
275        public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
276    
277        /** The resourceBundle for the localization. */
278        protected static ResourceBundle localizationResources 
279            = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
280    
281        /** The plot orientation. */
282        private PlotOrientation orientation;
283    
284        /** The offset between the data area and the axes. */
285        private RectangleInsets axisOffset;
286    
287        /** Storage for the domain axes. */
288        private ObjectList domainAxes;
289    
290        /** Storage for the domain axis locations. */
291        private ObjectList domainAxisLocations;
292    
293        /**
294         * A flag that controls whether or not the shared domain axis is drawn 
295         * (only relevant when the plot is being used as a subplot).
296         */
297        private boolean drawSharedDomainAxis;
298    
299        /** Storage for the range axes. */
300        private ObjectList rangeAxes;
301    
302        /** Storage for the range axis locations. */
303        private ObjectList rangeAxisLocations;
304    
305        /** Storage for the datasets. */
306        private ObjectList datasets;
307    
308        /** Storage for keys that map datasets to domain axes. */
309        private ObjectList datasetToDomainAxisMap;
310        
311        /** Storage for keys that map datasets to range axes. */
312        private ObjectList datasetToRangeAxisMap;
313    
314        /** Storage for the renderers. */
315        private ObjectList renderers;
316    
317        /** The dataset rendering order. */
318        private DatasetRenderingOrder renderingOrder 
319                = DatasetRenderingOrder.REVERSE;
320    
321        /** 
322         * Controls the order in which the columns are traversed when rendering the 
323         * data items. 
324         */
325        private SortOrder columnRenderingOrder = SortOrder.ASCENDING;
326        
327        /** 
328         * Controls the order in which the rows are traversed when rendering the 
329         * data items. 
330         */
331        private SortOrder rowRenderingOrder = SortOrder.ASCENDING;
332        
333        /** 
334         * A flag that controls whether the grid-lines for the domain axis are 
335         * visible. 
336         */
337        private boolean domainGridlinesVisible;
338    
339        /** The position of the domain gridlines relative to the category. */
340        private CategoryAnchor domainGridlinePosition;
341    
342        /** The stroke used to draw the domain grid-lines. */
343        private transient Stroke domainGridlineStroke;
344    
345        /** The paint used to draw the domain  grid-lines. */
346        private transient Paint domainGridlinePaint;
347    
348        /** 
349         * A flag that controls whether the grid-lines for the range axis are 
350         * visible. 
351         */
352        private boolean rangeGridlinesVisible;
353    
354        /** The stroke used to draw the range axis grid-lines. */
355        private transient Stroke rangeGridlineStroke;
356    
357        /** The paint used to draw the range axis grid-lines. */
358        private transient Paint rangeGridlinePaint;
359    
360        /** The anchor value. */
361        private double anchorValue;
362    
363        /** A flag that controls whether or not a range crosshair is drawn. */
364        private boolean rangeCrosshairVisible;
365    
366        /** The range crosshair value. */
367        private double rangeCrosshairValue;
368    
369        /** The pen/brush used to draw the crosshair (if any). */
370        private transient Stroke rangeCrosshairStroke;
371    
372        /** The color used to draw the crosshair (if any). */
373        private transient Paint rangeCrosshairPaint;
374    
375        /** 
376         * A flag that controls whether or not the crosshair locks onto actual 
377         * data points. 
378         */
379        private boolean rangeCrosshairLockedOnData = true;
380    
381        /** A map containing lists of markers for the domain axes. */
382        private Map foregroundDomainMarkers;
383    
384        /** A map containing lists of markers for the domain axes. */
385        private Map backgroundDomainMarkers;
386    
387        /** A map containing lists of markers for the range axes. */
388        private Map foregroundRangeMarkers;
389    
390        /** A map containing lists of markers for the range axes. */
391        private Map backgroundRangeMarkers;
392    
393        /** 
394         * A (possibly empty) list of annotations for the plot.  The list should
395         * be initialised in the constructor and never allowed to be 
396         * <code>null</code>.
397         */
398        private List annotations;
399    
400        /**
401         * The weight for the plot (only relevant when the plot is used as a subplot
402         * within a combined plot).
403         */
404        private int weight;
405    
406        /** The fixed space for the domain axis. */
407        private AxisSpace fixedDomainAxisSpace;
408    
409        /** The fixed space for the range axis. */
410        private AxisSpace fixedRangeAxisSpace;
411    
412        /** 
413         * An optional collection of legend items that can be returned by the 
414         * getLegendItems() method. 
415         */
416        private LegendItemCollection fixedLegendItems;
417        
418        /**
419         * Default constructor.
420         */
421        public CategoryPlot() {
422            this(null, null, null, null);
423        }
424    
425        /**
426         * Creates a new plot.
427         *
428         * @param dataset  the dataset (<code>null</code> permitted).
429         * @param domainAxis  the domain axis (<code>null</code> permitted).
430         * @param rangeAxis  the range axis (<code>null</code> permitted).
431         * @param renderer  the item renderer (<code>null</code> permitted).
432         *
433         */
434        public CategoryPlot(CategoryDataset dataset,
435                            CategoryAxis domainAxis,
436                            ValueAxis rangeAxis,
437                            CategoryItemRenderer renderer) {
438    
439            super();
440    
441            this.orientation = PlotOrientation.VERTICAL;
442    
443            // allocate storage for dataset, axes and renderers
444            this.domainAxes = new ObjectList();
445            this.domainAxisLocations = new ObjectList();
446            this.rangeAxes = new ObjectList();
447            this.rangeAxisLocations = new ObjectList();
448            
449            this.datasetToDomainAxisMap = new ObjectList();
450            this.datasetToRangeAxisMap = new ObjectList();
451    
452            this.renderers = new ObjectList();
453    
454            this.datasets = new ObjectList();
455            this.datasets.set(0, dataset);
456            if (dataset != null) {
457                dataset.addChangeListener(this);
458            }
459    
460            this.axisOffset = RectangleInsets.ZERO_INSETS;
461    
462            setDomainAxisLocation(AxisLocation.BOTTOM_OR_LEFT, false);
463            setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false);
464    
465            this.renderers.set(0, renderer);
466            if (renderer != null) {
467                renderer.setPlot(this);
468                renderer.addChangeListener(this);
469            }
470    
471            this.domainAxes.set(0, domainAxis);
472            this.mapDatasetToDomainAxis(0, 0);
473            if (domainAxis != null) {
474                domainAxis.setPlot(this);
475                domainAxis.addChangeListener(this);
476            }
477            this.drawSharedDomainAxis = false;
478    
479            this.rangeAxes.set(0, rangeAxis);
480            this.mapDatasetToRangeAxis(0, 0);
481            if (rangeAxis != null) {
482                rangeAxis.setPlot(this);
483                rangeAxis.addChangeListener(this);
484            }
485            
486            configureDomainAxes();
487            configureRangeAxes();
488    
489            this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE;
490            this.domainGridlinePosition = CategoryAnchor.MIDDLE;
491            this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
492            this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
493    
494            this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE;
495            this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
496            this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
497    
498            this.foregroundDomainMarkers = new HashMap();
499            this.backgroundDomainMarkers = new HashMap();
500            this.foregroundRangeMarkers = new HashMap();
501            this.backgroundRangeMarkers = new HashMap();
502    
503            Marker baseline = new ValueMarker(0.0, new Color(0.8f, 0.8f, 0.8f, 
504                    0.5f), new BasicStroke(1.0f), new Color(0.85f, 0.85f, 0.95f, 
505                    0.5f), new BasicStroke(1.0f), 0.6f);
506            addRangeMarker(baseline, Layer.BACKGROUND);
507    
508            this.anchorValue = 0.0;
509    
510            this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE;
511            this.rangeCrosshairValue = 0.0;
512            this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
513            this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
514            
515            this.annotations = new java.util.ArrayList();
516    
517        }
518        
519        /**
520         * Returns a string describing the type of plot.
521         *
522         * @return The type.
523         */
524        public String getPlotType() {
525            return localizationResources.getString("Category_Plot");
526        }
527    
528        /**
529         * Returns the orientation of the plot.
530         *
531         * @return The orientation of the plot (never <code>null</code>).
532         * 
533         * @see #setOrientation(PlotOrientation)
534         */
535        public PlotOrientation getOrientation() {
536            return this.orientation;
537        }
538    
539        /**
540         * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
541         * all registered listeners.
542         *
543         * @param orientation  the orientation (<code>null</code> not permitted).
544         * 
545         * @see #getOrientation()
546         */
547        public void setOrientation(PlotOrientation orientation) {
548            if (orientation == null) {
549                throw new IllegalArgumentException("Null 'orientation' argument.");
550            }
551            this.orientation = orientation;
552            notifyListeners(new PlotChangeEvent(this));
553        }
554    
555        /**
556         * Returns the axis offset.
557         *
558         * @return The axis offset (never <code>null</code>).
559         * 
560         * @see #setAxisOffset(RectangleInsets)
561         */
562        public RectangleInsets getAxisOffset() {
563            return this.axisOffset;
564        }
565    
566        /**
567         * Sets the axis offsets (gap between the data area and the axes) and
568         * sends a {@link PlotChangeEvent} to all registered listeners.
569         *
570         * @param offset  the offset (<code>null</code> not permitted).
571         * 
572         * @see #getAxisOffset()
573         */
574        public void setAxisOffset(RectangleInsets offset) {
575            if (offset == null) {
576                throw new IllegalArgumentException("Null 'offset' argument.");   
577            }
578            this.axisOffset = offset;
579            notifyListeners(new PlotChangeEvent(this));
580        }
581    
582        /**
583         * Returns the domain axis for the plot.  If the domain axis for this plot
584         * is <code>null</code>, then the method will return the parent plot's 
585         * domain axis (if there is a parent plot).
586         *
587         * @return The domain axis (<code>null</code> permitted).
588         * 
589         * @see #setDomainAxis(CategoryAxis)
590         */
591        public CategoryAxis getDomainAxis() {
592            return getDomainAxis(0);
593        }
594    
595        /**
596         * Returns a domain axis.
597         *
598         * @param index  the axis index.
599         *
600         * @return The axis (<code>null</code> possible).
601         * 
602         * @see #setDomainAxis(int, CategoryAxis)
603         */
604        public CategoryAxis getDomainAxis(int index) {
605            CategoryAxis result = null;
606            if (index < this.domainAxes.size()) {
607                result = (CategoryAxis) this.domainAxes.get(index);
608            }
609            if (result == null) {
610                Plot parent = getParent();
611                if (parent instanceof CategoryPlot) {
612                    CategoryPlot cp = (CategoryPlot) parent;
613                    result = cp.getDomainAxis(index);
614                }
615            }
616            return result;
617        }
618    
619        /**
620         * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to
621         * all registered listeners.
622         *
623         * @param axis  the axis (<code>null</code> permitted).
624         * 
625         * @see #getDomainAxis()
626         */
627        public void setDomainAxis(CategoryAxis axis) {
628            setDomainAxis(0, axis);
629        }
630    
631        /**
632         * Sets a domain axis and sends a {@link PlotChangeEvent} to all 
633         * registered listeners.
634         *
635         * @param index  the axis index.
636         * @param axis  the axis (<code>null</code> permitted).
637         * 
638         * @see #getDomainAxis(int)
639         */
640        public void setDomainAxis(int index, CategoryAxis axis) {
641            setDomainAxis(index, axis, true);
642        }
643     
644        /**
645         * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to 
646         * all registered listeners.
647         *
648         * @param index  the axis index.
649         * @param axis  the axis (<code>null</code> permitted).
650         * @param notify  notify listeners?
651         */
652        public void setDomainAxis(int index, CategoryAxis axis, boolean notify) {
653            CategoryAxis existing = (CategoryAxis) this.domainAxes.get(index);
654            if (existing != null) {
655                existing.removeChangeListener(this);
656            }
657            if (axis != null) {
658                axis.setPlot(this);
659            }
660            this.domainAxes.set(index, axis);
661            if (axis != null) {
662                axis.configure();
663                axis.addChangeListener(this);
664            }
665            if (notify) {
666                notifyListeners(new PlotChangeEvent(this));
667            }
668        }
669    
670        /**
671         * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
672         * to all registered listeners.
673         * 
674         * @param axes  the axes (<code>null</code> not permitted).
675         * 
676         * @see #setRangeAxes(ValueAxis[])
677         */
678        public void setDomainAxes(CategoryAxis[] axes) {
679            for (int i = 0; i < axes.length; i++) {
680                setDomainAxis(i, axes[i], false);   
681            }
682            notifyListeners(new PlotChangeEvent(this));
683        }
684        
685        /**
686         * Returns the index of the specified axis, or <code>-1</code> if the axis
687         * is not assigned to the plot.
688         * 
689         * @param axis  the axis.
690         * 
691         * @return The axis index.
692         * 
693         * @since 1.0.3
694         */
695        public int getDomainAxisIndex(CategoryAxis axis) {
696            return this.domainAxes.indexOf(axis);
697        }
698        
699        /**
700         * Returns the domain axis location for the primary domain axis.
701         *
702         * @return The location (never <code>null</code>).
703         * 
704         * @see #getRangeAxisLocation()
705         */
706        public AxisLocation getDomainAxisLocation() {
707            return getDomainAxisLocation(0);
708        }
709    
710        /**
711         * Returns the location for a domain axis.
712         *
713         * @param index  the axis index.
714         *
715         * @return The location.
716         * 
717         * @see #setDomainAxisLocation(int, AxisLocation)
718         */
719        public AxisLocation getDomainAxisLocation(int index) {
720            AxisLocation result = null;
721            if (index < this.domainAxisLocations.size()) {
722                result = (AxisLocation) this.domainAxisLocations.get(index);
723            }
724            if (result == null) {
725                result = AxisLocation.getOpposite(getDomainAxisLocation(0));
726            }
727            return result;
728        }
729    
730        /**
731         * Sets the location of the domain axis and sends a {@link PlotChangeEvent}
732         * to all registered listeners.
733         *
734         * @param location  the axis location (<code>null</code> not permitted).
735         * 
736         * @see #getDomainAxisLocation()
737         * @see #setDomainAxisLocation(int, AxisLocation)
738         */
739        public void setDomainAxisLocation(AxisLocation location) {
740            // delegate...
741            setDomainAxisLocation(0, location, true);
742        }
743    
744        /**
745         * Sets the location of the domain axis and, if requested, sends a 
746         * {@link PlotChangeEvent} to all registered listeners.
747         *
748         * @param location  the axis location (<code>null</code> not permitted).
749         * @param notify  a flag that controls whether listeners are notified.
750         */
751        public void setDomainAxisLocation(AxisLocation location, boolean notify) {
752            // delegate...
753            setDomainAxisLocation(0, location, notify);
754        }
755    
756        /**
757         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
758         * to all registered listeners.
759         *
760         * @param index  the axis index.
761         * @param location  the location.
762         * 
763         * @see #getDomainAxisLocation(int)
764         * @see #setRangeAxisLocation(int, AxisLocation)
765         */
766        public void setDomainAxisLocation(int index, AxisLocation location) {
767            // delegate...
768            setDomainAxisLocation(index, location, true);
769        }
770        
771        /**
772         * Sets the location for a domain axis and sends a {@link PlotChangeEvent} 
773         * to all registered listeners.
774         * 
775         * @param index  the axis index.
776         * @param location  the location.
777         * @param notify  notify listeners?
778         * 
779         * @since 1.0.5
780         * 
781         * @see #getDomainAxisLocation(int)
782         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
783         */
784        public void setDomainAxisLocation(int index, AxisLocation location, 
785                boolean notify) {
786            if (index == 0 && location == null) {
787                throw new IllegalArgumentException(
788                        "Null 'location' for index 0 not permitted.");
789            }
790            this.domainAxisLocations.set(index, location);
791            if (notify) {
792                notifyListeners(new PlotChangeEvent(this));
793            }
794        }
795    
796        /**
797         * Returns the domain axis edge.  This is derived from the axis location
798         * and the plot orientation.
799         *
800         * @return The edge (never <code>null</code>).
801         */
802        public RectangleEdge getDomainAxisEdge() {
803            return getDomainAxisEdge(0);
804        }
805    
806        /**
807         * Returns the edge for a domain axis.
808         *
809         * @param index  the axis index.
810         *
811         * @return The edge (never <code>null</code>).
812         */
813        public RectangleEdge getDomainAxisEdge(int index) {
814            RectangleEdge result = null;
815            AxisLocation location = getDomainAxisLocation(index);
816            if (location != null) {
817                result = Plot.resolveDomainAxisLocation(location, this.orientation);
818            }
819            else {
820                result = RectangleEdge.opposite(getDomainAxisEdge(0));
821            }
822            return result;
823        }
824    
825        /**
826         * Returns the number of domain axes.
827         *
828         * @return The axis count.
829         */
830        public int getDomainAxisCount() {
831            return this.domainAxes.size();
832        }
833    
834        /**
835         * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
836         * to all registered listeners.
837         */
838        public void clearDomainAxes() {
839            for (int i = 0; i < this.domainAxes.size(); i++) {
840                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
841                if (axis != null) {
842                    axis.removeChangeListener(this);
843                }
844            }
845            this.domainAxes.clear();
846            notifyListeners(new PlotChangeEvent(this));
847        }
848    
849        /**
850         * Configures the domain axes.
851         */
852        public void configureDomainAxes() {
853            for (int i = 0; i < this.domainAxes.size(); i++) {
854                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
855                if (axis != null) {
856                    axis.configure();
857                }
858            }
859        }
860    
861        /**
862         * Returns the range axis for the plot.  If the range axis for this plot is
863         * null, then the method will return the parent plot's range axis (if there
864         * is a parent plot).
865         *
866         * @return The range axis (possibly <code>null</code>).
867         */
868        public ValueAxis getRangeAxis() {
869            return getRangeAxis(0);
870        }
871    
872        /**
873         * Returns a range axis.
874         *
875         * @param index  the axis index.
876         *
877         * @return The axis (<code>null</code> possible).
878         */
879        public ValueAxis getRangeAxis(int index) {
880            ValueAxis result = null;
881            if (index < this.rangeAxes.size()) {
882                result = (ValueAxis) this.rangeAxes.get(index);
883            }
884            if (result == null) {
885                Plot parent = getParent();
886                if (parent instanceof CategoryPlot) {
887                    CategoryPlot cp = (CategoryPlot) parent;
888                    result = cp.getRangeAxis(index);
889                }
890            }
891            return result;
892        }
893    
894        /**
895         * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
896         * all registered listeners.
897         *
898         * @param axis  the axis (<code>null</code> permitted).
899         */
900        public void setRangeAxis(ValueAxis axis) {
901            setRangeAxis(0, axis);
902        }
903    
904        /**
905         * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
906         * listeners.
907         *
908         * @param index  the axis index.
909         * @param axis  the axis.
910         */
911        public void setRangeAxis(int index, ValueAxis axis) {
912            setRangeAxis(index, axis, true);
913        }
914            
915        /**
916         * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to 
917         * all registered listeners.
918         *
919         * @param index  the axis index.
920         * @param axis  the axis.
921         * @param notify  notify listeners?
922         */
923        public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
924            ValueAxis existing = (ValueAxis) this.rangeAxes.get(index);
925            if (existing != null) {
926                existing.removeChangeListener(this);
927            }
928            if (axis != null) {
929                axis.setPlot(this);
930            }
931            this.rangeAxes.set(index, axis);
932            if (axis != null) {
933                axis.configure();
934                axis.addChangeListener(this);
935            }
936            if (notify) {
937                notifyListeners(new PlotChangeEvent(this));
938            }
939        }
940    
941        /**
942         * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
943         * to all registered listeners.
944         * 
945         * @param axes  the axes (<code>null</code> not permitted).
946         * 
947         * @see #setDomainAxes(CategoryAxis[])
948         */
949        public void setRangeAxes(ValueAxis[] axes) {
950            for (int i = 0; i < axes.length; i++) {
951                setRangeAxis(i, axes[i], false);   
952            }
953            notifyListeners(new PlotChangeEvent(this));
954        }
955        
956        /**
957         * Returns the range axis location.
958         *
959         * @return The location (never <code>null</code>).
960         */
961        public AxisLocation getRangeAxisLocation() {
962            return getRangeAxisLocation(0);
963        }
964    
965        /**
966         * Returns the location for a range axis.
967         *
968         * @param index  the axis index.
969         *
970         * @return The location.
971         * 
972         * @see #setRangeAxisLocation(int, AxisLocation)
973         */
974        public AxisLocation getRangeAxisLocation(int index) {
975            AxisLocation result = null;
976            if (index < this.rangeAxisLocations.size()) {
977                result = (AxisLocation) this.rangeAxisLocations.get(index);
978            }
979            if (result == null) {
980                result = AxisLocation.getOpposite(getRangeAxisLocation(0));
981            }
982            return result;
983        }
984    
985        /**
986         * Sets the location of the range axis and sends a {@link PlotChangeEvent}
987         * to all registered listeners.
988         *
989         * @param location  the location (<code>null</code> not permitted).
990         * 
991         * @see #setRangeAxisLocation(AxisLocation, boolean)
992         * @see #setDomainAxisLocation(AxisLocation)
993         */
994        public void setRangeAxisLocation(AxisLocation location) {
995            // defer argument checking...
996            setRangeAxisLocation(location, true);
997        }
998    
999        /**
1000         * Sets the location of the range axis and, if requested, sends a 
1001         * {@link PlotChangeEvent} to all registered listeners.
1002         *
1003         * @param location  the location (<code>null</code> not permitted).
1004         * @param notify  notify listeners?
1005         * 
1006         * @see #setDomainAxisLocation(AxisLocation, boolean)
1007         */
1008        public void setRangeAxisLocation(AxisLocation location, boolean notify) {
1009            setRangeAxisLocation(0, location, notify);
1010        }
1011    
1012        /**
1013         * Sets the location for a range axis and sends a {@link PlotChangeEvent} 
1014         * to all registered listeners.
1015         *
1016         * @param index  the axis index.
1017         * @param location  the location.
1018         * 
1019         * @see #getRangeAxisLocation(int)
1020         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
1021         */
1022        public void setRangeAxisLocation(int index, AxisLocation location) {
1023            setRangeAxisLocation(index, location, true);
1024        }
1025    
1026        /**
1027         * Sets the location for a range axis and sends a {@link PlotChangeEvent} 
1028         * to all registered listeners.
1029         *
1030         * @param index  the axis index.
1031         * @param location  the location.
1032         * @param notify  notify listeners?
1033         * 
1034         * @see #getRangeAxisLocation(int)
1035         * @see #setDomainAxisLocation(int, AxisLocation, boolean)
1036         */
1037        public void setRangeAxisLocation(int index, AxisLocation location, 
1038                                         boolean notify) {
1039            if (index == 0 && location == null) {
1040                throw new IllegalArgumentException(
1041                        "Null 'location' for index 0 not permitted.");
1042            }
1043            this.rangeAxisLocations.set(index, location);
1044            if (notify) {
1045                notifyListeners(new PlotChangeEvent(this));
1046            }
1047        }
1048    
1049        /**
1050         * Returns the edge where the primary range axis is located.
1051         *
1052         * @return The edge (never <code>null</code>).
1053         */
1054        public RectangleEdge getRangeAxisEdge() {
1055            return getRangeAxisEdge(0);
1056        }
1057    
1058        /**
1059         * Returns the edge for a range axis.
1060         *
1061         * @param index  the axis index.
1062         *
1063         * @return The edge.
1064         */
1065        public RectangleEdge getRangeAxisEdge(int index) {
1066            AxisLocation location = getRangeAxisLocation(index);
1067            RectangleEdge result = Plot.resolveRangeAxisLocation(location, 
1068                    this.orientation);
1069            if (result == null) {
1070                result = RectangleEdge.opposite(getRangeAxisEdge(0));
1071            }
1072            return result;
1073        }
1074    
1075        /**
1076         * Returns the number of range axes.
1077         *
1078         * @return The axis count.
1079         */
1080        public int getRangeAxisCount() {
1081            return this.rangeAxes.size();
1082        }
1083    
1084        /**
1085         * Clears the range axes from the plot and sends a {@link PlotChangeEvent} 
1086         * to all registered listeners.
1087         */
1088        public void clearRangeAxes() {
1089            for (int i = 0; i < this.rangeAxes.size(); i++) {
1090                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1091                if (axis != null) {
1092                    axis.removeChangeListener(this);
1093                }
1094            }
1095            this.rangeAxes.clear();
1096            notifyListeners(new PlotChangeEvent(this));
1097        }
1098    
1099        /**
1100         * Configures the range axes.
1101         */
1102        public void configureRangeAxes() {
1103            for (int i = 0; i < this.rangeAxes.size(); i++) {
1104                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1105                if (axis != null) {
1106                    axis.configure();
1107                }
1108            }
1109        }
1110    
1111        /**
1112         * Returns the primary dataset for the plot.
1113         *
1114         * @return The primary dataset (possibly <code>null</code>).
1115         * 
1116         * @see #setDataset(CategoryDataset)
1117         */
1118        public CategoryDataset getDataset() {
1119            return getDataset(0);
1120        }
1121    
1122        /**
1123         * Returns the dataset at the given index.
1124         *
1125         * @param index  the dataset index.
1126         *
1127         * @return The dataset (possibly <code>null</code>).
1128         * 
1129         * @see #setDataset(int, CategoryDataset)
1130         */
1131        public CategoryDataset getDataset(int index) {
1132            CategoryDataset result = null;
1133            if (this.datasets.size() > index) {
1134                result = (CategoryDataset) this.datasets.get(index);
1135            }
1136            return result;
1137        }
1138    
1139        /**
1140         * Sets the dataset for the plot, replacing the existing dataset, if there 
1141         * is one.  This method also calls the 
1142         * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the 
1143         * axis ranges if necessary and sends a {@link PlotChangeEvent} to all 
1144         * registered listeners.
1145         *
1146         * @param dataset  the dataset (<code>null</code> permitted).
1147         * 
1148         * @see #getDataset()
1149         */
1150        public void setDataset(CategoryDataset dataset) {
1151            setDataset(0, dataset);
1152        }
1153    
1154        /**
1155         * Sets a dataset for the plot.
1156         *
1157         * @param index  the dataset index.
1158         * @param dataset  the dataset (<code>null</code> permitted).
1159         * 
1160         * @see #getDataset(int)
1161         */
1162        public void setDataset(int index, CategoryDataset dataset) {
1163            
1164            CategoryDataset existing = (CategoryDataset) this.datasets.get(index);
1165            if (existing != null) {
1166                existing.removeChangeListener(this);
1167            }
1168            this.datasets.set(index, dataset);
1169            if (dataset != null) {
1170                dataset.addChangeListener(this);
1171            }
1172            
1173            // send a dataset change event to self...
1174            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
1175            datasetChanged(event);
1176            
1177        }
1178    
1179        /**
1180         * Returns the number of datasets.
1181         *
1182         * @return The number of datasets.
1183         * 
1184         * @since 1.0.2
1185         */
1186        public int getDatasetCount() {
1187            return this.datasets.size();
1188        }
1189    
1190        /**
1191         * Maps a dataset to a particular domain axis.
1192         * 
1193         * @param index  the dataset index (zero-based).
1194         * @param axisIndex  the axis index (zero-based).
1195         * 
1196         * @see #getDomainAxisForDataset(int)
1197         */
1198        public void mapDatasetToDomainAxis(int index, int axisIndex) {
1199            this.datasetToDomainAxisMap.set(index, new Integer(axisIndex));  
1200            // fake a dataset change event to update axes...
1201            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));  
1202        }
1203    
1204        /**
1205         * Returns the domain axis for a dataset.  You can change the axis for a 
1206         * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method.
1207         * 
1208         * @param index  the dataset index.
1209         * 
1210         * @return The domain axis.
1211         * 
1212         * @see #mapDatasetToDomainAxis(int, int)
1213         */
1214        public CategoryAxis getDomainAxisForDataset(int index) {
1215            CategoryAxis result = getDomainAxis();
1216            Integer axisIndex = (Integer) this.datasetToDomainAxisMap.get(index);
1217            if (axisIndex != null) {
1218                result = getDomainAxis(axisIndex.intValue());
1219            }
1220            return result;    
1221        }
1222        
1223        /**
1224         * Maps a dataset to a particular range axis.
1225         * 
1226         * @param index  the dataset index (zero-based).
1227         * @param axisIndex  the axis index (zero-based).
1228         * 
1229         * @see #getRangeAxisForDataset(int)
1230         */
1231        public void mapDatasetToRangeAxis(int index, int axisIndex) {
1232            this.datasetToRangeAxisMap.set(index, new Integer(axisIndex));
1233            // fake a dataset change event to update axes...
1234            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));  
1235        }
1236    
1237        /**
1238         * Returns the range axis for a dataset.  You can change the axis for a 
1239         * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method.
1240         * 
1241         * @param index  the dataset index.
1242         * 
1243         * @return The range axis.
1244         * 
1245         * @see #mapDatasetToRangeAxis(int, int)
1246         */
1247        public ValueAxis getRangeAxisForDataset(int index) {
1248            ValueAxis result = getRangeAxis();
1249            Integer axisIndex = (Integer) this.datasetToRangeAxisMap.get(index);
1250            if (axisIndex != null) {
1251                result = getRangeAxis(axisIndex.intValue());
1252            }
1253            return result;    
1254        }
1255        
1256        /**
1257         * Returns a reference to the renderer for the plot.
1258         *
1259         * @return The renderer.
1260         * 
1261         * @see #setRenderer(CategoryItemRenderer)
1262         */
1263        public CategoryItemRenderer getRenderer() {
1264            return getRenderer(0);
1265        }
1266    
1267        /**
1268         * Returns the renderer at the given index.
1269         *
1270         * @param index  the renderer index.
1271         *
1272         * @return The renderer (possibly <code>null</code>).
1273         * 
1274         * @see #setRenderer(int, CategoryItemRenderer)
1275         */
1276        public CategoryItemRenderer getRenderer(int index) {
1277            CategoryItemRenderer result = null;
1278            if (this.renderers.size() > index) {
1279                result = (CategoryItemRenderer) this.renderers.get(index);
1280            }
1281            return result;
1282        }
1283        
1284        /**
1285         * Sets the renderer at index 0 (sometimes referred to as the "primary" 
1286         * renderer) and sends a {@link PlotChangeEvent} to all registered 
1287         * listeners.
1288         *
1289         * @param renderer  the renderer (<code>null</code> permitted.
1290         * 
1291         * @see #getRenderer()
1292         */
1293        public void setRenderer(CategoryItemRenderer renderer) {
1294            setRenderer(0, renderer, true);
1295        }
1296    
1297        /**
1298         * Sets the renderer at index 0 (sometimes referred to as the "primary" 
1299         * renderer) and, if requested, sends a {@link PlotChangeEvent} to all 
1300         * registered listeners.
1301         * <p>
1302         * You can set the renderer to <code>null</code>, but this is not 
1303         * recommended because:
1304         * <ul>
1305         *   <li>no data will be displayed;</li>
1306         *   <li>the plot background will not be painted;</li>
1307         * </ul>
1308         *
1309         * @param renderer  the renderer (<code>null</code> permitted).
1310         * @param notify  notify listeners?
1311         * 
1312         * @see #getRenderer()
1313         */
1314        public void setRenderer(CategoryItemRenderer renderer, boolean notify) {
1315            setRenderer(0, renderer, notify);
1316        }
1317    
1318        /**
1319         * Sets the renderer at the specified index and sends a 
1320         * {@link PlotChangeEvent} to all registered listeners.
1321         *
1322         * @param index  the index.
1323         * @param renderer  the renderer (<code>null</code> permitted).
1324         * 
1325         * @see #getRenderer(int)
1326         * @see #setRenderer(int, CategoryItemRenderer, boolean)
1327         */
1328        public void setRenderer(int index, CategoryItemRenderer renderer) {
1329            setRenderer(index, renderer, true);   
1330        }
1331    
1332        /**
1333         * Sets a renderer.  A {@link PlotChangeEvent} is sent to all registered 
1334         * listeners.
1335         *
1336         * @param index  the index.
1337         * @param renderer  the renderer (<code>null</code> permitted).
1338         * @param notify  notify listeners?
1339         * 
1340         * @see #getRenderer(int)
1341         */
1342        public void setRenderer(int index, CategoryItemRenderer renderer, 
1343                                boolean notify) {
1344            
1345            // stop listening to the existing renderer...
1346            CategoryItemRenderer existing 
1347                = (CategoryItemRenderer) this.renderers.get(index);
1348            if (existing != null) {
1349                existing.removeChangeListener(this);
1350            }
1351            
1352            // register the new renderer...
1353            this.renderers.set(index, renderer);
1354            if (renderer != null) {
1355                renderer.setPlot(this);
1356                renderer.addChangeListener(this);
1357            }
1358            
1359            configureDomainAxes();
1360            configureRangeAxes();
1361            
1362            if (notify) {
1363                notifyListeners(new PlotChangeEvent(this));
1364            }
1365        }
1366    
1367        /**
1368         * Sets the renderers for this plot and sends a {@link PlotChangeEvent}
1369         * to all registered listeners.
1370         * 
1371         * @param renderers  the renderers.
1372         */
1373        public void setRenderers(CategoryItemRenderer[] renderers) {
1374            for (int i = 0; i < renderers.length; i++) {
1375                setRenderer(i, renderers[i], false);   
1376            }
1377            notifyListeners(new PlotChangeEvent(this));
1378        }
1379        
1380        /**
1381         * Returns the renderer for the specified dataset.  If the dataset doesn't
1382         * belong to the plot, this method will return <code>null</code>.
1383         * 
1384         * @param dataset  the dataset (<code>null</code> permitted).
1385         * 
1386         * @return The renderer (possibly <code>null</code>).
1387         */
1388        public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) {
1389            CategoryItemRenderer result = null;
1390            for (int i = 0; i < this.datasets.size(); i++) {
1391                if (this.datasets.get(i) == dataset) {
1392                    result = (CategoryItemRenderer) this.renderers.get(i);   
1393                    break;
1394                }
1395            }
1396            return result;
1397        }
1398        
1399        /**
1400         * Returns the index of the specified renderer, or <code>-1</code> if the
1401         * renderer is not assigned to this plot.
1402         * 
1403         * @param renderer  the renderer (<code>null</code> permitted).
1404         * 
1405         * @return The renderer index.
1406         */
1407        public int getIndexOf(CategoryItemRenderer renderer) {
1408            return this.renderers.indexOf(renderer);
1409        }
1410    
1411        /**
1412         * Returns the dataset rendering order.
1413         *
1414         * @return The order (never <code>null</code>).
1415         * 
1416         * @see #setDatasetRenderingOrder(DatasetRenderingOrder)
1417         */
1418        public DatasetRenderingOrder getDatasetRenderingOrder() {
1419            return this.renderingOrder;
1420        }
1421    
1422        /**
1423         * Sets the rendering order and sends a {@link PlotChangeEvent} to all 
1424         * registered listeners.  By default, the plot renders the primary dataset 
1425         * last (so that the primary dataset overlays the secondary datasets).  You