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