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