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