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