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