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