001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025     * Other names may be trademarks of their respective owners.]
026     *
027     * -----------------
028     * CategoryPlot.java
029     * -----------------
030     * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Jeremy Bowman;
034     *                   Arnaud Lelievre;
035     *                   Richard West, Advanced Micro Devices, Inc.;
036     *                   Ulrich Voigt - patch 2686040;
037     *                   Peter Kolb - patches 2603321 and 2809117;
038     *
039     * Changes
040     * -------
041     * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG);
042     * 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG);
043     * 18-Sep-2001 : Updated header (DG);
044     * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG);
045     * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
046     * 23-Oct-2001 : Changed intro and trail gaps on bar plots to use percentage of
047     *               available space rather than a fixed number of units (DG);
048     * 12-Dec-2001 : Changed constructors to protected (DG);
049     * 13-Dec-2001 : Added tooltips (DG);
050     * 16-Jan-2002 : Increased maximum intro and trail gap percents, plus added
051     *               some argument checking code.  Thanks to Taoufik Romdhane for
052     *               suggesting this (DG);
053     * 05-Feb-2002 : Added accessor methods for the tooltip generator, incorporated
054     *               alpha-transparency for Plot and subclasses (DG);
055     * 06-Mar-2002 : Updated import statements (DG);
056     * 14-Mar-2002 : Renamed BarPlot.java --> CategoryPlot.java, and changed code
057     *               to use the CategoryItemRenderer interface (DG);
058     * 22-Mar-2002 : Dropped the getCategories() method (DG);
059     * 23-Apr-2002 : Moved the dataset from the JFreeChart class to the Plot
060     *               class (DG);
061     * 29-Apr-2002 : New methods to support printing values at the end of bars,
062     *               contributed by Jeremy Bowman (DG);
063     * 11-May-2002 : New methods for label visibility and overlaid plot support,
064     *               contributed by Jeremy Bowman (DG);
065     * 06-Jun-2002 : Removed the tooltip generator, this is now stored with the
066     *               renderer.  Moved constants into the CategoryPlotConstants
067     *               interface.  Updated Javadoc comments (DG);
068     * 10-Jun-2002 : Overridden datasetChanged() method to update the upper and
069     *               lower bound on the range axis (if necessary), updated
070     *               Javadocs (DG);
071     * 25-Jun-2002 : Removed redundant imports (DG);
072     * 20-Aug-2002 : Changed the constructor for Marker (DG);
073     * 28-Aug-2002 : Added listener notification to setDomainAxis() and
074     *               setRangeAxis() (DG);
075     * 23-Sep-2002 : Added getLegendItems() method and fixed errors reported by
076     *               Checkstyle (DG);
077     * 28-Oct-2002 : Changes to the CategoryDataset interface (DG);
078     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
079     * 07-Nov-2002 : Renamed labelXXX as valueLabelXXX (DG);
080     * 18-Nov-2002 : Added grid settings for both domain and range axis (previously
081     *               these were set in the axes) (DG);
082     * 19-Nov-2002 : Added axis location parameters to constructor (DG);
083     * 17-Jan-2003 : Moved to com.jrefinery.chart.plot package (DG);
084     * 14-Feb-2003 : Fixed bug in auto-range calculation for secondary axis (DG);
085     * 26-Mar-2003 : Implemented Serializable (DG);
086     * 02-May-2003 : Moved render() method up from subclasses. Added secondary
087     *               range markers. Added an attribute to control the dataset
088     *               rendering order.  Added a drawAnnotations() method.  Changed
089     *               the axis location from an int to an AxisLocation (DG);
090     * 07-May-2003 : Merged HorizontalCategoryPlot and VerticalCategoryPlot into
091     *               this class (DG);
092     * 02-Jun-2003 : Removed check for range axis compatibility (DG);
093     * 04-Jul-2003 : Added a domain gridline position attribute (DG);
094     * 21-Jul-2003 : Moved DrawingSupplier to Plot superclass (DG);
095     * 19-Aug-2003 : Added equals() method and implemented Cloneable (DG);
096     * 01-Sep-2003 : Fixed bug 797466 (no change event when secondary dataset
097     *               changes) (DG);
098     * 02-Sep-2003 : Fixed bug 795209 (wrong dataset checked in render2 method) and
099     *               790407 (initialise method) (DG);
100     * 08-Sep-2003 : Added internationalization via use of properties
101     *               resourceBundle (RFE 690236) (AL);
102     * 08-Sep-2003 : Fixed bug (wrong secondary range axis being used).  Changed
103     *               ValueAxis API (DG);
104     * 10-Sep-2003 : Fixed bug in setRangeAxis() method (DG);
105     * 15-Sep-2003 : Fixed two bugs in serialization, implemented
106     *               PublicCloneable (DG);
107     * 23-Oct-2003 : Added event notification for changes to renderer (DG);
108     * 26-Nov-2003 : Fixed bug (849645) in clearRangeMarkers() method (DG);
109     * 03-Dec-2003 : Modified draw method to accept anchor (DG);
110     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
111     * 10-Mar-2004 : Fixed bug in axis range calculation when secondary renderer is
112     *               stacked (DG);
113     * 12-May-2004 : Added fixed legend items (DG);
114     * 19-May-2004 : Added check for null legend item from renderer (DG);
115     * 02-Jun-2004 : Updated the DatasetRenderingOrder class (DG);
116     * 05-Nov-2004 : Renamed getDatasetsMappedToRangeAxis()
117     *               --> datasetsMappedToRangeAxis(), and ensured that returned
118     *               list doesn't contain null datasets (DG);
119     * 12-Nov-2004 : Implemented new Zoomable interface (DG);
120     * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() in
121     *               CategoryItemRenderer (DG);
122     * 04-May-2005 : Fixed serialization of range markers (DG);
123     * 05-May-2005 : Updated draw() method parameters (DG);
124     * 20-May-2005 : Added setDomainAxes() and setRangeAxes() methods, as per
125     *               RFE 1183100 (DG);
126     * 01-Jun-2005 : Upon deserialization, register plot as a listener with its
127     *               axes, dataset(s) and renderer(s) - see patch 1209475 (DG);
128     * 02-Jun-2005 : Added support for domain markers (DG);
129     * 06-Jun-2005 : Fixed equals() method for use with GradientPaint (DG);
130     * 09-Jun-2005 : Added setRenderers(), as per RFE 1183100 (DG);
131     * 16-Jun-2005 : Added getDomainAxisCount() and getRangeAxisCount() methods, to
132     *               match XYPlot (see RFE 1220495) (DG);
133     * ------------- JFREECHART 1.0.x ---------------------------------------------
134     * 11-Jan-2006 : Added configureRangeAxes() to rendererChanged(), since the
135     *               renderer might influence the axis range (DG);
136     * 27-Jan-2006 : Added various null argument checks (DG);
137     * 18-Aug-2006 : Added getDatasetCount() method, plus a fix for bug drawing
138     *               category labels, thanks to Adriaan Joubert (1277726) (DG);
139     * 05-Sep-2006 : Added MarkerChangeEvent support (DG);
140     * 30-Oct-2006 : Added getDomainAxisIndex(), datasetsMappedToDomainAxis() and
141     *               getCategoriesForAxis() methods (DG);
142     * 22-Nov-2006 : Fire PlotChangeEvent from setColumnRenderingOrder() and
143     *               setRowRenderingOrder() (DG);
144     * 29-Nov-2006 : Fix for bug 1605207 (IntervalMarker exceeds bounds of data
145     *               area) (DG);
146     * 26-Feb-2007 : Fix for bug 1669218 (setDomainAxisLocation() notify argument
147     *               ignored) (DG);
148     * 13-Mar-2007 : Added null argument checks for setRangeCrosshairPaint() and
149     *               setRangeCrosshairStroke(), fixed clipping for
150     *               annotations (DG);
151     * 07-Jun-2007 : Override drawBackground() for new GradientPaint handling (DG);
152     * 10-Jul-2007 : Added getRangeAxisIndex(ValueAxis) method (DG);
153     * 24-Sep-2007 : Implemented new zoom methods (DG);
154     * 25-Oct-2007 : Added some argument checks (DG);
155     * 05-Nov-2007 : Applied patch 1823697, by Richard West, for removal of domain
156     *               and range markers (DG);
157     * 14-Nov-2007 : Added missing event notifications (DG);
158     * 25-Mar-2008 : Added new methods with optional notification - see patch
159     *               1913751 (DG);
160     * 07-Apr-2008 : Fixed NPE in removeDomainMarker() and
161     *               removeRangeMarker() (DG);
162     * 23-Apr-2008 : Fixed equals() and clone() methods (DG);
163     * 26-Jun-2008 : Fixed crosshair support (DG);
164     * 10-Jul-2008 : Fixed outline visibility for 3D renderers (DG);
165     * 12-Aug-2008 : Added rendererCount() method (DG);
166     * 25-Nov-2008 : Added facility to map datasets to multiples axes (DG);
167     * 15-Dec-2008 : Cleaned up grid drawing methods (DG);
168     * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
169     *               Jess Thrysoee (DG);
170     * 21-Jan-2009 : Added rangeMinorGridlinesVisible flag (DG);
171     * 18-Mar-2009 : Modified anchored zoom behaviour (DG);
172     * 19-Mar-2009 : Implemented Pannable interface - see patch 2686040 (DG);
173     * 19-Mar-2009 : Added entity support - see patch 2603321 by Peter Kolb (DG);
174     * 24-Jun-2009 : Implemented AnnotationChangeListener (see patch 2809117 by
175     *               PK) (DG);
176     * 06-Jul-2009 : Fix for cloning of renderers - see bug 2817504 (DG)
177     * 10-Jul-2009 : Added optional drop shadow generator (DG);
178     * 27-Sep-2011 : Fixed annotation import (DG);
179     * 18-Oct-2011 : Fixed tooltip offset with shadow generator (DG);
180     * 20-Nov-2011 : Initialise shadow generator as null (DG);
181     *
182     */
183    
184    package org.jfree.chart.plot;
185    
186    import java.awt.AlphaComposite;
187    import java.awt.BasicStroke;
188    import java.awt.Color;
189    import java.awt.Composite;
190    import java.awt.Font;
191    import java.awt.Graphics2D;
192    import java.awt.Paint;
193    import java.awt.Rectangle;
194    import java.awt.Shape;
195    import java.awt.Stroke;
196    import java.awt.geom.Line2D;
197    import java.awt.geom.Point2D;
198    import java.awt.geom.Rectangle2D;
199    import java.awt.image.BufferedImage;
200    import java.io.IOException;
201    import java.io.ObjectInputStream;
202    import java.io.ObjectOutputStream;
203    import java.io.Serializable;
204    import java.util.ArrayList;
205    import java.util.Collection;
206    import java.util.Collections;
207    import java.util.HashMap;
208    import java.util.HashSet;
209    import java.util.Iterator;
210    import java.util.List;
211    import java.util.Map;
212    import java.util.ResourceBundle;
213    import java.util.Set;
214    import java.util.TreeMap;
215    import org.jfree.chart.LegendItemCollection;
216    import org.jfree.chart.annotations.Annotation;
217    import org.jfree.chart.annotations.CategoryAnnotation;
218    import org.jfree.chart.axis.Axis;
219    import org.jfree.chart.axis.AxisCollection;
220    import org.jfree.chart.axis.AxisLocation;
221    import org.jfree.chart.axis.AxisSpace;
222    import org.jfree.chart.axis.AxisState;
223    import org.jfree.chart.axis.CategoryAnchor;
224    import org.jfree.chart.axis.CategoryAxis;
225    import org.jfree.chart.axis.TickType;
226    import org.jfree.chart.axis.ValueAxis;
227    import org.jfree.chart.axis.ValueTick;
228    import org.jfree.chart.event.AnnotationChangeEvent;
229    import org.jfree.chart.event.AnnotationChangeListener;
230    import org.jfree.chart.event.ChartChangeEventType;
231    import org.jfree.chart.event.PlotChangeEvent;
232    import org.jfree.chart.event.RendererChangeEvent;
233    import org.jfree.chart.event.RendererChangeListener;
234    import org.jfree.chart.renderer.category.AbstractCategoryItemRenderer;
235    import org.jfree.chart.renderer.category.CategoryItemRenderer;
236    import org.jfree.chart.renderer.category.CategoryItemRendererState;
237    import org.jfree.chart.util.ResourceBundleWrapper;
238    import org.jfree.chart.util.ShadowGenerator;
239    import org.jfree.data.Range;
240    import org.jfree.data.category.CategoryDataset;
241    import org.jfree.data.general.Dataset;
242    import org.jfree.data.general.DatasetChangeEvent;
243    import org.jfree.data.general.DatasetUtilities;
244    import org.jfree.io.SerialUtilities;
245    import org.jfree.ui.Layer;
246    import org.jfree.ui.RectangleEdge;
247    import org.jfree.ui.RectangleInsets;
248    import org.jfree.util.ObjectList;
249    import org.jfree.util.ObjectUtilities;
250    import org.jfree.util.PaintUtilities;
251    import org.jfree.util.PublicCloneable;
252    import org.jfree.util.ShapeUtilities;
253    import org.jfree.util.SortOrder;
254    
255    /**
256     * A general plotting class that uses data from a {@link CategoryDataset} and
257     * renders each data item using a {@link CategoryItemRenderer}.
258     */
259    public class CategoryPlot extends Plot implements ValueAxisPlot, Pannable,
260            Zoomable, AnnotationChangeListener, RendererChangeListener, 
261            Cloneable, PublicCloneable, Serializable {
262    
263        /** For serialization. */
264        private static final long serialVersionUID = -3537691700434728188L;
265    
266        /**
267         * The default visibility of the grid lines plotted against the domain
268         * axis.
269         */
270        public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false;
271    
272        /**
273         * The default visibility of the grid lines plotted against the range
274         * axis.
275         */
276        public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true;
277    
278        /** The default grid line stroke. */
279        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
280                BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[]
281                {2.0f, 2.0f}, 0.0f);
282    
283        /** The default grid line paint. */
284        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
285    
286        /** The default value label font. */
287        public static final Font DEFAULT_VALUE_LABEL_FONT = new Font("SansSerif",
288                Font.PLAIN, 10);
289    
290        /**
291         * The default crosshair visibility.
292         *
293         * @since 1.0.5
294         */
295        public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
296    
297        /**
298         * The default crosshair stroke.
299         *
300         * @since 1.0.5
301         */
302        public static final Stroke DEFAULT_CROSSHAIR_STROKE
303                = DEFAULT_GRIDLINE_STROKE;
304    
305        /**
306         * The default crosshair paint.
307         *
308         * @since 1.0.5
309         */
310        public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
311    
312        /** The resourceBundle for the localization. */
313        protected static ResourceBundle localizationResources
314                = ResourceBundleWrapper.getBundle(
315                "org.jfree.chart.plot.LocalizationBundle");
316    
317        /** The plot orientation. */
318        private PlotOrientation orientation;
319    
320        /** The offset between the data area and the axes. */
321        private RectangleInsets axisOffset;
322    
323        /** Storage for the domain axes. */
324        private ObjectList domainAxes;
325    
326        /** Storage for the domain axis locations. */
327        private ObjectList domainAxisLocations;
328    
329        /**
330         * A flag that controls whether or not the shared domain axis is drawn
331         * (only relevant when the plot is being used as a subplot).
332         */
333        private boolean drawSharedDomainAxis;
334    
335        /** Storage for the range axes. */
336        private ObjectList rangeAxes;
337    
338        /** Storage for the range axis locations. */
339        private ObjectList rangeAxisLocations;
340    
341        /** Storage for the datasets. */
342        private ObjectList datasets;
343    
344        /** Storage for keys that map datasets to domain axes. */
345        private TreeMap datasetToDomainAxesMap;
346    
347        /** Storage for keys that map datasets to range axes. */
348        private TreeMap datasetToRangeAxesMap;
349    
350        /** Storage for the renderers. */
351        private ObjectList renderers;
352    
353        /** The dataset rendering order. */
354        private DatasetRenderingOrder renderingOrder
355                = DatasetRenderingOrder.REVERSE;
356    
357        /**
358         * Controls the order in which the columns are traversed when rendering the
359         * data items.
360         */
361        private SortOrder columnRenderingOrder = SortOrder.ASCENDING;
362    
363        /**
364         * Controls the order in which the rows are traversed when rendering the
365         * data items.
366         */
367        private SortOrder rowRenderingOrder = SortOrder.ASCENDING;
368    
369        /**
370         * A flag that controls whether the grid-lines for the domain axis are
371         * visible.
372         */
373        private boolean domainGridlinesVisible;
374    
375        /** The position of the domain gridlines relative to the category. */
376        private CategoryAnchor domainGridlinePosition;
377    
378        /** The stroke used to draw the domain grid-lines. */
379        private transient Stroke domainGridlineStroke;
380    
381        /** The paint used to draw the domain  grid-lines. */
382        private transient Paint domainGridlinePaint;
383    
384        /**
385         * A flag that controls whether or not the zero baseline against the range
386         * axis is visible.
387         *
388         * @since 1.0.13
389         */
390        private boolean rangeZeroBaselineVisible;
391    
392        /**
393         * The stroke used for the zero baseline against the range axis.
394         *
395         * @since 1.0.13
396         */
397        private transient Stroke rangeZeroBaselineStroke;
398    
399        /**
400         * The paint used for the zero baseline against the range axis.
401         *
402         * @since 1.0.13
403         */
404        private transient Paint rangeZeroBaselinePaint;
405    
406        /**
407         * A flag that controls whether the grid-lines for the range axis are
408         * visible.
409         */
410        private boolean rangeGridlinesVisible;
411    
412        /** The stroke used to draw the range axis grid-lines. */
413        private transient Stroke rangeGridlineStroke;
414    
415        /** The paint used to draw the range axis grid-lines. */
416        private transient Paint rangeGridlinePaint;
417    
418        /**
419         * A flag that controls whether or not gridlines are shown for the minor
420         * tick values on the primary range axis.
421         *
422         * @since 1.0.13
423         */
424        private boolean rangeMinorGridlinesVisible;
425    
426        /**
427         * The stroke used to draw the range minor grid-lines.
428         *
429         * @since 1.0.13
430         */
431        private transient Stroke rangeMinorGridlineStroke;
432    
433        /**
434         * The paint used to draw the range minor grid-lines.
435         *
436         * @since 1.0.13
437         */
438        private transient Paint rangeMinorGridlinePaint;
439    
440        /** The anchor value. */
441        private double anchorValue;
442    
443        /**
444         * The index for the dataset that the crosshairs are linked to (this
445         * determines which axes the crosshairs are plotted against).
446         *
447         * @since 1.0.11
448         */
449        private int crosshairDatasetIndex;
450    
451        /**
452         * A flag that controls the visibility of the domain crosshair.
453         *
454         * @since 1.0.11
455         */
456        private boolean domainCrosshairVisible;
457    
458        /**
459         * The row key for the crosshair point.
460         *
461         * @since 1.0.11
462         */
463        private Comparable domainCrosshairRowKey;
464    
465        /**
466         * The column key for the crosshair point.
467         *
468         * @since 1.0.11
469         */
470        private Comparable domainCrosshairColumnKey;
471    
472        /**
473         * The stroke used to draw the domain crosshair if it is visible.
474         *
475         * @since 1.0.11
476         */
477        private transient Stroke domainCrosshairStroke;
478    
479        /**
480         * The paint used to draw the domain crosshair if it is visible.
481         *
482         * @since 1.0.11
483         */
484        private transient Paint domainCrosshairPaint;
485    
486        /** A flag that controls whether or not a range crosshair is drawn. */
487        private boolean rangeCrosshairVisible;
488    
489        /** The range crosshair value. */
490        private double rangeCrosshairValue;
491    
492        /** The pen/brush used to draw the crosshair (if any). */
493        private transient Stroke rangeCrosshairStroke;
494    
495        /** The color used to draw the crosshair (if any). */
496        private transient Paint rangeCrosshairPaint;
497    
498        /**
499         * A flag that controls whether or not the crosshair locks onto actual
500         * data points.
501         */
502        private boolean rangeCrosshairLockedOnData = true;
503    
504        /** A map containing lists of markers for the domain axes. */
505        private Map foregroundDomainMarkers;
506    
507        /** A map containing lists of markers for the domain axes. */
508        private Map backgroundDomainMarkers;
509    
510        /** A map containing lists of markers for the range axes. */
511        private Map foregroundRangeMarkers;
512    
513        /** A map containing lists of markers for the range axes. */
514        private Map backgroundRangeMarkers;
515    
516        /**
517         * A (possibly empty) list of annotations for the plot.  The list should
518         * be initialised in the constructor and never allowed to be
519         * <code>null</code>.
520         */
521        private List annotations;
522    
523        /**
524         * The weight for the plot (only relevant when the plot is used as a subplot
525         * within a combined plot).
526         */
527        private int weight;
528    
529        /** The fixed space for the domain axis. */
530        private AxisSpace fixedDomainAxisSpace;
531    
532        /** The fixed space for the range axis. */
533        private AxisSpace fixedRangeAxisSpace;
534    
535        /**
536         * An optional collection of legend items that can be returned by the
537         * getLegendItems() method.
538         */
539        private LegendItemCollection fixedLegendItems;
540    
541        /**
542         * A flag that controls whether or not panning is enabled for the 
543         * range axis/axes.
544         *
545         * @since 1.0.13
546         */
547        private boolean rangePannable;
548    
549        /**
550         * The shadow generator for the plot (<code>null</code> permitted).
551         * 
552         * @since 1.0.14
553         */
554        private ShadowGenerator shadowGenerator;
555    
556        /**
557         * Default constructor.
558         */
559        public CategoryPlot() {
560            this(null, null, null, null);
561        }
562    
563        /**
564         * Creates a new plot.
565         *
566         * @param dataset  the dataset (<code>null</code> permitted).
567         * @param domainAxis  the domain axis (<code>null</code> permitted).
568         * @param rangeAxis  the range axis (<code>null</code> permitted).
569         * @param renderer  the item renderer (<code>null</code> permitted).
570         *
571         */
572        public CategoryPlot(CategoryDataset dataset,
573                            CategoryAxis domainAxis,
574                            ValueAxis rangeAxis,
575                            CategoryItemRenderer renderer) {
576    
577            super();
578    
579            this.orientation = PlotOrientation.VERTICAL;
580    
581            // allocate storage for dataset, axes and renderers
582            this.domainAxes = new ObjectList();
583            this.domainAxisLocations = new ObjectList();
584            this.rangeAxes = new ObjectList();
585            this.rangeAxisLocations = new ObjectList();
586    
587            this.datasetToDomainAxesMap = new TreeMap();
588            this.datasetToRangeAxesMap = new TreeMap();
589    
590            this.renderers = new ObjectList();
591    
592            this.datasets = new ObjectList();
593            this.datasets.set(0, dataset);
594            if (dataset != null) {
595                dataset.addChangeListener(this);
596            }
597    
598            this.axisOffset = RectangleInsets.ZERO_INSETS;
599    
600            setDomainAxisLocation(AxisLocation.BOTTOM_OR_LEFT, false);
601            setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false);
602    
603            this.renderers.set(0, renderer);
604            if (renderer != null) {
605                renderer.setPlot(this);
606                renderer.addChangeListener(this);
607            }
608    
609            this.domainAxes.set(0, domainAxis);
610            this.mapDatasetToDomainAxis(0, 0);
611            if (domainAxis != null) {
612                domainAxis.setPlot(this);
613                domainAxis.addChangeListener(this);
614            }
615            this.drawSharedDomainAxis = false;
616    
617            this.rangeAxes.set(0, rangeAxis);
618            this.mapDatasetToRangeAxis(0, 0);
619            if (rangeAxis != null) {
620                rangeAxis.setPlot(this);
621                rangeAxis.addChangeListener(this);
622            }
623    
624            configureDomainAxes();
625            configureRangeAxes();
626    
627            this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE;
628            this.domainGridlinePosition = CategoryAnchor.MIDDLE;
629            this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
630            this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
631    
632            this.rangeZeroBaselineVisible = false;
633            this.rangeZeroBaselinePaint = Color.black;
634            this.rangeZeroBaselineStroke = new BasicStroke(0.5f);
635    
636            this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE;
637            this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
638            this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
639    
640            this.rangeMinorGridlinesVisible = false;
641            this.rangeMinorGridlineStroke = DEFAULT_GRIDLINE_STROKE;
642            this.rangeMinorGridlinePaint = Color.white;
643    
644            this.foregroundDomainMarkers = new HashMap();
645            this.backgroundDomainMarkers = new HashMap();
646            this.foregroundRangeMarkers = new HashMap();
647            this.backgroundRangeMarkers = new HashMap();
648    
649            this.anchorValue = 0.0;
650    
651            this.domainCrosshairVisible = false;
652            this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
653            this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
654    
655            this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE;
656            this.rangeCrosshairValue = 0.0;
657            this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
658            this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
659    
660            this.annotations = new java.util.ArrayList();
661            
662            this.rangePannable = false;
663            this.shadowGenerator = null;
664        }
665    
666        /**
667         * Returns a string describing the type of plot.
668         *
669         * @return The type.
670         */
671        public String getPlotType() {
672            return localizationResources.getString("Category_Plot");
673        }
674    
675        /**
676         * Returns the orientation of the plot.
677         *
678         * @return The orientation of the plot (never <code>null</code>).
679         *
680         * @see #setOrientation(PlotOrientation)
681         */
682        public PlotOrientation getOrientation() {
683            return this.orientation;
684        }
685    
686        /**
687         * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
688         * all registered listeners.
689         *
690         * @param orientation  the orientation (<code>null</code> not permitted).
691         *
692         * @see #getOrientation()
693         */
694        public void setOrientation(PlotOrientation orientation) {
695            if (orientation == null) {
696                throw new IllegalArgumentException("Null 'orientation' argument.");
697            }
698            this.orientation = orientation;
699            fireChangeEvent();
700        }
701    
702        /**
703         * Returns the axis offset.
704         *
705         * @return The axis offset (never <code>null</code>).
706         *
707         * @see #setAxisOffset(RectangleInsets)
708         */
709        public RectangleInsets getAxisOffset() {
710            return this.axisOffset;
711        }
712    
713        /**
714         * Sets the axis offsets (gap between the data area and the axes) and
715         * sends a {@link PlotChangeEvent} to all registered listeners.
716         *
717         * @param offset  the offset (<code>null</code> not permitted).
718         *
719         * @see #getAxisOffset()
720         */
721        public void setAxisOffset(RectangleInsets offset) {
722            if (offset == null) {
723                throw new IllegalArgumentException("Null 'offset' argument.");
724            }
725            this.axisOffset = offset;
726            fireChangeEvent();
727        }
728    
729        /**
730         * Returns the domain axis for the plot.  If the domain axis for this plot
731         * is <code>null</code>, then the method will return the parent plot's
732         * domain axis (if there is a parent plot).
733         *
734         * @return The domain axis (<code>null</code> permitted).
735         *
736         * @see #setDomainAxis(CategoryAxis)
737         */
738        public CategoryAxis getDomainAxis() {
739            return getDomainAxis(0);
740        }
741    
742        /**
743         * Returns a domain axis.
744         *
745         * @param index  the axis index.
746         *
747         * @return The axis (<code>null</code> possible).
748         *
749         * @see #setDomainAxis(int, CategoryAxis)
750         */
751        public CategoryAxis getDomainAxis(int index) {
752            CategoryAxis result = null;
753            if (index < this.domainAxes.size()) {
754                result = (CategoryAxis) this.domainAxes.get(index);
755            }
756            if (result == null) {
757                Plot parent = getParent();
758                if (parent instanceof CategoryPlot) {
759                    CategoryPlot cp = (CategoryPlot) parent;
760                    result = cp.getDomainAxis(index);
761                }
762            }
763            return result;
764        }
765    
766        /**
767         * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to
768         * all registered listeners.
769         *
770         * @param axis  the axis (<code>null</code> permitted).
771         *
772         * @see #getDomainAxis()
773         */
774        public void setDomainAxis(CategoryAxis axis) {
775            setDomainAxis(0, axis);
776        }
777    
778        /**
779         * Sets a domain axis and sends a {@link PlotChangeEvent} to all
780         * registered listeners.
781         *
782         * @param index  the axis index.
783         * @param axis  the axis (<code>null</code> permitted).
784         *
785         * @see #getDomainAxis(int)
786         */
787        public void setDomainAxis(int index, CategoryAxis axis) {
788            setDomainAxis(index, axis, true);
789        }
790    
791        /**
792         * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
793         * all registered listeners.
794         *
795         * @param index  the axis index.
796         * @param axis  the axis (<code>null</code> permitted).
797         * @param notify  notify listeners?
798         */
799        public void setDomainAxis(int index, CategoryAxis axis, boolean notify) {
800            CategoryAxis existing = (CategoryAxis) this.domainAxes.get(index);
801            if (existing != null) {
802                existing.removeChangeListener(this);
803            }
804            if (axis != null) {
805                axis.setPlot(this);
806            }
807            this.domainAxes.set(index, axis);
808            if (axis != null) {
809                axis.configure();
810                axis.addChangeListener(this);
811            }
812            if (notify) {
813                fireChangeEvent();
814            }
815        }
816    
817        /**
818         * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
819         * to all registered listeners.
820         *
821         * @param axes  the axes (<code>null</code> not permitted).
822         *
823         * @see #setRangeAxes(ValueAxis[])
824         */
825        public void setDomainAxes(CategoryAxis[] axes) {
826            for (int i = 0; i < axes.length; i++) {
827                setDomainAxis(i, axes[i], false);
828            }
829            fireChangeEvent();
830        }
831    
832        /**
833         * Returns the index of the specified axis, or <code>-1</code> if the axis
834         * is not assigned to the plot.
835         *
836         * @param axis  the axis (<code>null</code> not permitted).
837         *
838         * @return The axis index.
839         *
840         * @see #getDomainAxis(int)
841         * @see #getRangeAxisIndex(ValueAxis)
842         *
843         * @since 1.0.3
844         */
845        public int getDomainAxisIndex(CategoryAxis axis) {
846            if (axis == null) {
847                throw new IllegalArgumentException("Null 'axis' argument.");
848            }
849            return this.domainAxes.indexOf(axis);
850        }
851    
852        /**
853         * Returns the domain axis location for the primary domain axis.
854         *
855         * @return The location (never <code>null</code>).
856         *
857         * @see #getRangeAxisLocation()
858         */
859        public AxisLocation getDomainAxisLocation() {
860            return getDomainAxisLocation(0);
861        }
862    
863        /**
864         * Returns the location for a domain axis.
865         *
866         * @param index  the axis index.
867         *
868         * @return The location.
869         *
870         * @see #setDomainAxisLocation(int, AxisLocation)
871         */
872        public AxisLocation getDomainAxisLocation(int index) {
873            AxisLocation result = null;
874            if (index < this.domainAxisLocations.size()) {
875                result = (AxisLocation) this.domainAxisLocations.get(index);
876            }
877            if (result == null) {
878                result = AxisLocation.getOpposite(getDomainAxisLocation(0));
879            }
880            return result;
881        }
882    
883        /**
884         * Sets the location of the domain axis and sends a {@link PlotChangeEvent}
885         * to all registered listeners.
886         *
887         * @param location  the axis location (<code>null</code> not permitted).
888         *
889         * @see #getDomainAxisLocation()
890         * @see #setDomainAxisLocation(int, AxisLocation)
891         */
892        public void setDomainAxisLocation(AxisLocation location) {
893            // delegate...
894            setDomainAxisLocation(0, location, true);
895        }
896    
897        /**
898         * Sets the location of the domain axis and, if requested, sends a
899         * {@link PlotChangeEvent} to all registered listeners.
900         *
901         * @param location  the axis location (<code>null</code> not permitted).
902         * @param notify  a flag that controls whether listeners are notified.
903         */
904        public void setDomainAxisLocation(AxisLocation location, boolean notify) {
905            // delegate...
906            setDomainAxisLocation(0, location, notify);
907        }
908    
909        /**
910         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
911         * to all registered listeners.
912         *
913         * @param index  the axis index.
914         * @param location  the location.
915         *
916         * @see #getDomainAxisLocation(int)
917         * @see #setRangeAxisLocation(int, AxisLocation)
918         */
919        public void setDomainAxisLocation(int index, AxisLocation location) {
920            // delegate...
921            setDomainAxisLocation(index, location, true);
922        }
923    
924        /**
925         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
926         * to all registered listeners.
927         *
928         * @param index  the axis index.
929         * @param location  the location.
930         * @param notify  notify listeners?
931         *
932         * @since 1.0.5
933         *
934         * @see #getDomainAxisLocation(int)
935         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
936         */
937        public void setDomainAxisLocation(int index, AxisLocation location,
938                boolean notify) {
939            if (index == 0 && location == null) {
940                throw new IllegalArgumentException(
941                        "Null 'location' for index 0 not permitted.");
942            }
943            this.domainAxisLocations.set(index, location);
944            if (notify) {
945                fireChangeEvent();
946            }
947        }
948    
949        /**
950         * Returns the domain axis edge.  This is derived from the axis location
951         * and the plot orientation.
952         *
953         * @return The edge (never <code>null</code>).
954         */
955        public RectangleEdge getDomainAxisEdge() {
956            return getDomainAxisEdge(0);
957        }
958    
959        /**
960         * Returns the edge for a domain axis.
961         *
962         * @param index  the axis index.
963         *
964         * @return The edge (never <code>null</code>).
965         */
966        public RectangleEdge getDomainAxisEdge(int index) {
967            RectangleEdge result = null;
968            AxisLocation location = getDomainAxisLocation(index);
969            if (location != null) {
970                result = Plot.resolveDomainAxisLocation(location, this.orientation);
971            }
972            else {
973                result = RectangleEdge.opposite(getDomainAxisEdge(0));
974            }
975            return result;
976        }
977    
978        /**
979         * Returns the number of domain axes.
980         *
981         * @return The axis count.
982         */
983        public int getDomainAxisCount() {
984            return this.domainAxes.size();
985        }
986    
987        /**
988         * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
989         * to all registered listeners.
990         */
991        public void clearDomainAxes() {
992            for (int i = 0; i < this.domainAxes.size(); i++) {
993                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
994                if (axis != null) {
995                    axis.removeChangeListener(this);
996                }
997            }
998            this.domainAxes.clear();
999            fireChangeEvent();
1000        }
1001    
1002        /**
1003         * Configures the domain axes.
1004         */
1005        public void configureDomainAxes() {
1006            for (int i = 0; i < this.domainAxes.size(); i++) {
1007                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
1008                if (axis != null) {
1009                    axis.configure();
1010                }
1011            }
1012        }
1013    
1014        /**
1015         * Returns the range axis for the plot.  If the range axis for this plot is
1016         * null, then the method will return the parent plot's range axis (if there
1017         * is a parent plot).
1018         *
1019         * @return The range axis (possibly <code>null</code>).
1020         */
1021        public ValueAxis getRangeAxis() {
1022            return getRangeAxis(0);
1023        }
1024    
1025        /**
1026         * Returns a range axis.
1027         *
1028         * @param index  the axis index.
1029         *
1030         * @return The axis (<code>null</code> possible).
1031         */
1032        public ValueAxis getRangeAxis(int index) {
1033            ValueAxis result = null;
1034            if (index < this.rangeAxes.size()) {
1035                result = (ValueAxis) this.rangeAxes.get(index);
1036            }
1037            if (result == null) {
1038                Plot parent = getParent();
1039                if (parent instanceof CategoryPlot) {
1040                    CategoryPlot cp = (CategoryPlot) parent;
1041                    result = cp.getRangeAxis(index);
1042                }
1043            }
1044            return result;
1045        }
1046    
1047        /**
1048         * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
1049         * all registered listeners.
1050         *
1051         * @param axis  the axis (<code>null</code> permitted).
1052         */
1053        public void setRangeAxis(ValueAxis axis) {
1054            setRangeAxis(0, axis);
1055        }
1056    
1057        /**
1058         * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
1059         * listeners.
1060         *
1061         * @param index  the axis index.
1062         * @param axis  the axis.
1063         */
1064        public void setRangeAxis(int index, ValueAxis axis) {
1065            setRangeAxis(index, axis, true);
1066        }
1067    
1068        /**
1069         * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
1070         * all registered listeners.
1071         *
1072         * @param index  the axis index.
1073         * @param axis  the axis.
1074         * @param notify  notify listeners?
1075         */
1076        public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
1077            ValueAxis existing = (ValueAxis) this.rangeAxes.get(index);
1078            if (existing != null) {
1079                existing.removeChangeListener(this);
1080            }
1081            if (axis != null) {
1082                axis.setPlot(this);
1083            }
1084            this.rangeAxes.set(index, axis);
1085            if (axis != null) {
1086                axis.configure();
1087                axis.addChangeListener(this);
1088            }
1089            if (notify) {
1090                fireChangeEvent();
1091            }
1092        }
1093    
1094        /**
1095         * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
1096         * to all registered listeners.
1097         *
1098         * @param axes  the axes (<code>null</code> not permitted).
1099         *
1100         * @see #setDomainAxes(CategoryAxis[])
1101         */
1102        public void setRangeAxes(ValueAxis[] axes) {
1103            for (int i = 0; i < axes.length; i++) {
1104                setRangeAxis(i, axes[i], false);
1105            }
1106            fireChangeEvent();
1107        }
1108    
1109        /**
1110         * Returns the index of the specified axis, or <code>-1</code> if the axis
1111         * is not assigned to the plot.
1112         *
1113         * @param axis  the axis (<code>null</code> not permitted).
1114         *
1115         * @return The axis index.
1116         *
1117         * @see #getRangeAxis(int)
1118         * @see #getDomainAxisIndex(CategoryAxis)
1119         *
1120         * @since 1.0.7
1121         */
1122        public int getRangeAxisIndex(ValueAxis axis) {
1123            if (axis == null) {
1124                throw new IllegalArgumentException("Null 'axis' argument.");
1125            }
1126            int result = this.rangeAxes.indexOf(axis);
1127            if (result < 0) { // try the parent plot
1128                Plot parent = getParent();
1129                if (parent instanceof CategoryPlot) {
1130                    CategoryPlot p = (CategoryPlot) parent;
1131                    result = p.getRangeAxisIndex(axis);
1132                }
1133            }
1134            return result;
1135        }
1136    
1137        /**
1138         * Returns the range axis location.
1139         *
1140         * @return The location (never <code>null</code>).
1141         */
1142        public AxisLocation getRangeAxisLocation() {
1143            return getRangeAxisLocation(0);
1144        }
1145    
1146        /**
1147         * Returns the location for a range axis.
1148         *
1149         * @param index  the axis index.
1150         *
1151         * @return The location.
1152         *
1153         * @see #setRangeAxisLocation(int, AxisLocation)
1154         */
1155        public AxisLocation getRangeAxisLocation(int index) {
1156            AxisLocation result = null;
1157            if (index < this.rangeAxisLocations.size()) {
1158                result = (AxisLocation) this.rangeAxisLocations.get(index);
1159            }
1160            if (result == null) {
1161                result = AxisLocation.getOpposite(getRangeAxisLocation(0));
1162            }
1163            return result;
1164        }
1165    
1166        /**
1167         * Sets the location of the range axis and sends a {@link PlotChangeEvent}
1168         * to all registered listeners.
1169         *
1170         * @param location  the location (<code>null</code> not permitted).
1171         *
1172         * @see #setRangeAxisLocation(AxisLocation, boolean)
1173         * @see #setDomainAxisLocation(AxisLocation)
1174         */
1175        public void setRangeAxisLocation(AxisLocation location) {
1176            // defer argument checking...
1177            setRangeAxisLocation(location, true);
1178        }
1179    
1180        /**
1181         * Sets the location of the range axis and, if requested, sends a
1182         * {@link PlotChangeEvent} to all registered listeners.
1183         *
1184         * @param location  the location (<code>null</code> not permitted).
1185         * @param notify  notify listeners?
1186         *
1187         * @see #setDomainAxisLocation(AxisLocation, boolean)
1188         */
1189        public void setRangeAxisLocation(AxisLocation location, boolean notify) {
1190            setRangeAxisLocation(0, location, notify);
1191        }
1192    
1193        /**
1194         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
1195         * to all registered listeners.
1196         *
1197         * @param index  the axis index.
1198         * @param location  the location.
1199         *
1200         * @see #getRangeAxisLocation(int)
1201         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
1202         */
1203        public void setRangeAxisLocation(int index, AxisLocation location) {
1204            setRangeAxisLocation(index, location, true);
1205        }
1206    
1207        /**
1208         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
1209         * to all registered listeners.
1210         *
1211         * @param index  the axis index.
1212         * @param location  the location.
1213         * @param notify  notify listeners?
1214         *
1215         * @see #getRangeAxisLocation(int)
1216         * @see #setDomainAxisLocation(int, AxisLocation, boolean)
1217         */
1218        public void setRangeAxisLocation(int index, AxisLocation location,
1219                                         boolean notify) {
1220            if (index == 0 && location == null) {
1221                throw new IllegalArgumentException(
1222                        "Null 'location' for index 0 not permitted.");
1223            }
1224            this.rangeAxisLocations.set(index, location);
1225            if (notify) {
1226                fireChangeEvent();
1227            }
1228        }
1229    
1230        /**
1231         * Returns the edge where the primary range axis is located.
1232         *
1233         * @return The edge (never <code>null</code>).
1234         */
1235        public RectangleEdge getRangeAxisEdge() {
1236            return getRangeAxisEdge(0);
1237        }
1238    
1239        /**
1240         * Returns the edge for a range axis.
1241         *
1242         * @param index  the axis index.
1243         *
1244         * @return The edge.
1245         */
1246        public RectangleEdge getRangeAxisEdge(int index) {
1247            AxisLocation location = getRangeAxisLocation(index);
1248            RectangleEdge result = Plot.resolveRangeAxisLocation(location,
1249                    this.orientation);
1250            if (result == null) {
1251                result = RectangleEdge.opposite(getRangeAxisEdge(0));
1252            }
1253            return result;
1254        }
1255    
1256        /**
1257         * Returns the number of range axes.
1258         *
1259         * @return The axis count.
1260         */
1261        public int getRangeAxisCount() {
1262            return this.rangeAxes.size();
1263        }
1264    
1265        /**
1266         * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
1267         * to all registered listeners.
1268         */
1269        public void clearRangeAxes() {
1270            for (int i = 0; i < this.rangeAxes.size(); i++) {
1271                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1272                if (axis != null) {
1273                    axis.removeChangeListener(this);
1274                }
1275            }
1276            this.rangeAxes.clear();
1277            fireChangeEvent();
1278        }
1279    
1280        /**
1281         * Configures the range axes.
1282         */
1283        public void configureRangeAxes() {
1284            for (int i = 0; i < this.rangeAxes.size(); i++) {
1285                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1286                if (axis != null) {
1287                    axis.configure();
1288                }
1289            }
1290        }
1291    
1292        /**
1293         * Returns the primary dataset for the plot.
1294         *
1295         * @return The primary dataset (possibly <code>null</code>).
1296         *
1297         * @see #setDataset(CategoryDataset)
1298         */
1299        public CategoryDataset getDataset() {
1300            return getDataset(0);
1301        }
1302    
1303        /**
1304         * Returns the dataset at the given index.
1305         *
1306         * @param index  the dataset index.
1307         *
1308         * @return The dataset (possibly <code>null</code>).
1309         *
1310         * @see #setDataset(int, CategoryDataset)
1311         */
1312        public CategoryDataset getDataset(int index) {
1313            CategoryDataset result = null;
1314            if (this.datasets.size() > index) {
1315                result = (CategoryDataset) this.datasets.get(index);
1316            }
1317            return result;
1318        }
1319    
1320        /**
1321         * Sets the dataset for the plot, replacing the existing dataset, if there
1322         * is one.  This method also calls the
1323         * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the
1324         * axis ranges if necessary and sends a {@link PlotChangeEvent} to all
1325         * registered listeners.
1326         *
1327         * @param dataset  the dataset (<code>null</code> permitted).
1328         *
1329         * @see #getDataset()
1330         */
1331        public void setDataset(CategoryDataset dataset) {
1332            setDataset(0, dataset);
1333        }
1334    
1335        /**
1336         * Sets a dataset for the plot.
1337         *
1338         * @param index  the dataset index.
1339         * @param dataset  the dataset (<code>null</code> permitted).
1340         *
1341         * @see #getDataset(int)
1342         */
1343        public void setDataset(int index, CategoryDataset dataset) {
1344    
1345            CategoryDataset existing = (CategoryDataset) this.datasets.get(index);
1346            if (existing != null) {
1347                existing.removeChangeListener(this);
1348            }
1349            this.datasets.set(index, dataset);
1350            if (dataset != null) {
1351                dataset.addChangeListener(this);
1352            }
1353    
1354            // send a dataset change event to self...
1355            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
1356            datasetChanged(event);
1357    
1358        }
1359    
1360        /**
1361         * Returns the number of datasets.
1362         *
1363         * @return The number of datasets.
1364         *
1365         * @since 1.0.2
1366         */
1367        public int getDatasetCount() {
1368            return this.datasets.size();
1369        }
1370    
1371        /**
1372         * Returns the index of the specified dataset, or <code>-1</code> if the
1373         * dataset does not belong to the plot.
1374         *
1375         * @param dataset  the dataset (<code>null</code> not permitted).
1376         *
1377         * @return The index.
1378         *
1379         * @since 1.0.11
1380         */
1381        public int indexOf(CategoryDataset dataset) {
1382            int result = -1;
1383            for (int i = 0; i < this.datasets.size(); i++) {
1384                if (dataset == this.datasets.get(i)) {
1385                    result = i;
1386                    break;
1387                }
1388            }
1389            return result;
1390        }
1391    
1392        /**
1393         * Maps a dataset to a particular domain axis.
1394         *
1395         * @param index  the dataset index (zero-based).
1396         * @param axisIndex  the axis index (zero-based).
1397         *
1398         * @see #getDomainAxisForDataset(int)
1399         */
1400        public void mapDatasetToDomainAxis(int index, int axisIndex) {
1401            List axisIndices = new java.util.ArrayList(1);
1402            axisIndices.add(new Integer(axisIndex));
1403            mapDatasetToDomainAxes(index, axisIndices);
1404        }
1405    
1406        /**
1407         * Maps the specified dataset to the axes in the list.  Note that the
1408         * conversion of data values into Java2D space is always performed using
1409         * the first axis in the list.
1410         *
1411         * @param index  the dataset index (zero-based).
1412         * @param axisIndices  the axis indices (<code>null</code> permitted).
1413         *
1414         * @since 1.0.12
1415         */
1416        public void mapDatasetToDomainAxes(int index, List axisIndices) {
1417            if (index < 0) {
1418                throw new IllegalArgumentException("Requires 'index' >= 0.");
1419            }
1420            checkAxisIndices(axisIndices);
1421            Integer key = new Integer(index);
1422            this.datasetToDomainAxesMap.put(key, new ArrayList(axisIndices));
1423            // fake a dataset change event to update axes...
1424            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1425        }
1426    
1427        /**
1428         * This method is used to perform argument checking on the list of
1429         * axis indices passed to mapDatasetToDomainAxes() and
1430         * mapDatasetToRangeAxes().
1431         *
1432         * @param indices  the list of indices (<code>null</code> permitted).
1433         */
1434        private void checkAxisIndices(List indices) {
1435            // axisIndices can be:
1436            // 1.  null;
1437            // 2.  non-empty, containing only Integer objects that are unique.
1438            if (indices == null) {
1439                return;  // OK
1440            }
1441            int count = indices.size();
1442            if (count == 0) {
1443                throw new IllegalArgumentException("Empty list not permitted.");
1444            }
1445            HashSet set = new HashSet();
1446            for (int i = 0; i < count; i++) {
1447                Object item = indices.get(i);
1448                if (!(item instanceof Integer)) {
1449                    throw new IllegalArgumentException(
1450                            "Indices must be Integer instances.");
1451                }
1452                if (set.contains(item)) {
1453                    throw new IllegalArgumentException("Indices must be unique.");
1454                }
1455                set.add(item);
1456            }
1457        }
1458    
1459        /**
1460         * Returns the domain axis for a dataset.  You can change the axis for a
1461         * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method.
1462         *
1463         * @param index  the dataset index.
1464         *
1465         * @return The domain axis.
1466         *
1467         * @see #mapDatasetToDomainAxis(int, int)
1468         */
1469        public CategoryAxis getDomainAxisForDataset(int index) {
1470            if (index < 0) {
1471                throw new IllegalArgumentException("Negative 'index'.");
1472            }
1473            CategoryAxis axis = null;
1474            List axisIndices = (List) this.datasetToDomainAxesMap.get(
1475                    new Integer(index));
1476            if (axisIndices != null) {
1477                // the first axis in the list is used for data <--> Java2D
1478                Integer axisIndex = (Integer) axisIndices.get(0);
1479                axis = getDomainAxis(axisIndex.intValue());
1480            }
1481            else {
1482                axis = getDomainAxis(0);
1483            }
1484            return axis;
1485        }
1486    
1487        /**
1488         * Maps a dataset to a particular range axis.
1489         *
1490         * @param index  the dataset index (zero-based).
1491         * @param axisIndex  the axis index (zero-based).
1492         *
1493         * @see #getRangeAxisForDataset(int)
1494         */
1495        public void mapDatasetToRangeAxis(int index, int axisIndex) {
1496            List axisIndices = new java.util.ArrayList(1);
1497            axisIndices.add(new Integer(axisIndex));
1498            mapDatasetToRangeAxes(index, axisIndices);
1499        }
1500    
1501        /**
1502         * Maps the specified dataset to the axes in the list.  Note that the
1503         * conversion of data values into Java2D space is always performed using
1504         * the first axis in the list.
1505         *
1506         * @param index  the dataset index (zero-based).
1507         * @param axisIndices  the axis indices (<code>null</code> permitted).
1508         *
1509         * @since 1.0.12
1510         */
1511        public void mapDatasetToRangeAxes(int index, List axisIndices) {
1512            if (index < 0) {
1513                throw new IllegalArgumentException("Requires 'index' >= 0.");
1514            }
1515            checkAxisIndices(axisIndices);
1516            Integer key = new Integer(index);
1517            this.datasetToRangeAxesMap.put(key, new ArrayList(axisIndices));
1518            // fake a dataset change event to update axes...
1519            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1520        }
1521    
1522        /**
1523         * Returns the range axis for a dataset.  You can change the axis for a
1524         * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method.
1525         *
1526         * @param index  the dataset index.
1527         *
1528         * @return The range axis.
1529         *
1530         * @see #mapDatasetToRangeAxis(int, int)
1531         */
1532        public ValueAxis getRangeAxisForDataset(int index) {
1533            if (index < 0) {
1534                throw new IllegalArgumentException("Negative 'index'.");
1535            }
1536            ValueAxis axis = null;
1537            List axisIndices = (List) this.datasetToRangeAxesMap.get(
1538                    new Integer(index));
1539            if (axisIndices != null) {
1540                // the first axis in the list is used for data <--> Java2D
1541                Integer axisIndex = (Integer) axisIndices.get(0);
1542                axis = getRangeAxis(axisIndex.intValue());
1543            }
1544            else {
1545                axis = getRangeAxis(0);
1546            }
1547            return axis;
1548        }
1549    
1550        /**
1551         * Returns the number of renderer slots for this plot.
1552         *
1553         * @return The number of renderer slots.
1554         *
1555         * @since 1.0.11
1556         */
1557        public int getRendererCount() {
1558            return this.renderers.size();
1559        }
1560    
1561        /**
1562         * Returns a reference to the renderer for the plot.
1563         *
1564         * @return The renderer.
1565         *
1566         * @see #setRenderer(CategoryItemRenderer)
1567         */
1568        public CategoryItemRenderer getRenderer() {
1569            return getRenderer(0);
1570        }
1571    
1572        /**
1573         * Returns the renderer at the given index.
1574         *
1575         * @param index  the renderer index.
1576         *
1577         * @return The renderer (possibly <code>null</code>).
1578         *
1579         * @see #setRenderer(int, CategoryItemRenderer)
1580         */
1581        public CategoryItemRenderer getRenderer(int index) {
1582            CategoryItemRenderer result = null;
1583            if (this.renderers.size() > index) {
1584                result = (CategoryItemRenderer) this.renderers.get(index);
1585            }
1586            return result;
1587        }
1588    
1589        /**
1590         * Sets the renderer at index 0 (sometimes referred to as the "primary"
1591         * renderer) and sends a {@link PlotChangeEvent} to all registered
1592         * listeners.
1593         *
1594         * @param renderer  the renderer (<code>null</code> permitted.
1595         *
1596         * @see #getRenderer()
1597         */
1598        public void setRenderer(CategoryItemRenderer renderer) {
1599            setRenderer(0, renderer, true);
1600        }
1601    
1602        /**
1603         * Sets the renderer at index 0 (sometimes referred to as the "primary"
1604         * renderer) and, if requested, sends a {@link PlotChangeEvent} to all
1605         * registered listeners.
1606         * <p>
1607         * You can set the renderer to <code>null</code>, but this is not
1608         * recommended because:
1609         * <ul>
1610         *   <li>no data will be displayed;</li>
1611         *   <li>the plot background will not be painted;</li>
1612         * </ul>
1613         *
1614         * @param renderer  the renderer (<code>null</code> permitted).
1615         * @param notify  notify listeners?
1616         *
1617         * @see #getRenderer()
1618         */
1619        public void setRenderer(CategoryItemRenderer renderer, boolean notify) {
1620            setRenderer(0, renderer, notify);
1621        }
1622    
1623        /**
1624         * Sets the renderer at the specified index and sends a
1625         * {@link PlotChangeEvent} to all registered listeners.
1626         *
1627         * @param index  the index.
1628         * @param renderer  the renderer (<code>null</code> permitted).
1629         *
1630         * @see #getRenderer(int)
1631         * @see #setRenderer(int, CategoryItemRenderer, boolean)
1632         */
1633        public void setRenderer(int index, CategoryItemRenderer renderer) {
1634            setRenderer(index, renderer, true);
1635        }
1636    
1637        /**
1638         * Sets a renderer.  A {@link PlotChangeEvent} is sent to all registered
1639         * listeners.
1640         *
1641         * @param index  the index.
1642         * @param renderer  the renderer (<code>null</code> permitted).
1643         * @param notify  notify listeners?
1644         *
1645         * @see #getRenderer(int)
1646         */
1647        public void setRenderer(int index, CategoryItemRenderer renderer,
1648                                boolean notify) {
1649    
1650            // stop listening to the existing renderer...
1651            CategoryItemRenderer existing
1652                = (CategoryItemRenderer) this.renderers.get(index);
1653            if (existing != null) {
1654                existing.removeChangeListener(this);
1655            }
1656    
1657            // register the new renderer...
1658            this.renderers.set(index, renderer);
1659            if (renderer != null) {
1660                renderer.setPlot(this);
1661                renderer.addChangeListener(this);
1662            }
1663    
1664            configureDomainAxes();
1665            configureRangeAxes();
1666    
1667            if (notify) {
1668                fireChangeEvent();
1669            }
1670        }
1671    
1672        /**
1673         * Sets the renderers for this plot and sends a {@link PlotChangeEvent}
1674         * to all registered listeners.
1675         *
1676         * @param renderers  the renderers.
1677         */
1678        public void setRenderers(CategoryItemRenderer[] renderers) {
1679            for (int i = 0; i < renderers.length; i++) {
1680                setRenderer(i, renderers[i], false);
1681            }
1682            fireChangeEvent();
1683        }
1684    
1685        /**
1686         * Returns the renderer for the specified dataset.  If the dataset doesn't
1687         * belong to the plot, this method will return <code>null</code>.
1688         *
1689         * @param dataset  the dataset (<code>null</code> permitted).
1690         *
1691         * @return The renderer (possibly <code>null</code>).
1692         */
1693        public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) {
1694            CategoryItemRenderer result = null;
1695            for (int i = 0; i < this.datasets.size(); i++) {
1696                if (this.datasets.get(i) == dataset) {
1697                    result = (CategoryItemRenderer) this.renderers.get(i);
1698                    break;
1699                }
1700            }
1701            return result;
1702        }
1703    
1704        /**
1705         * Returns the index of the specified renderer, or <code>-1</code> if the
1706         * renderer is not assigned to this plot.
1707         *
1708         * @param renderer  the renderer (<code>null</code> permitted).
1709         *
1710         * @return The renderer index.
1711         */
1712        public int getIndexOf(CategoryItemRenderer renderer) {
1713            return this.renderers.indexOf(renderer);
1714        }
1715    
1716        /**
1717         * Returns the dataset rendering order.
1718         *
1719         * @return The order (never <code>null</code>).
1720         *
1721         * @see #setDatasetRenderingOrder(DatasetRenderingOrder)
1722         */
1723        public DatasetRenderingOrder getDatasetRenderingOrder() {
1724            return this.renderingOrder;
1725        }
1726    
1727        /**
1728         * Sets the rendering order and sends a {@link PlotChangeEvent} to all
1729         * registered listeners.  By default, the plot renders the primary dataset
1730         * last (so that the primary dataset overlays the secondary datasets).  You
1731         * can reverse this if you want to.
1732         *
1733         * @param order  the rendering order (<code>null</code> not permitted).
1734         *
1735         * @see #getDatasetRenderingOrder()
1736         */
1737        public void setDatasetRenderingOrder(DatasetRenderingOrder order) {
1738            if (order == null) {
1739                throw new IllegalArgumentException("Null 'order' argument.");
1740            }
1741            this.renderingOrder = order;
1742            fireChangeEvent();
1743        }
1744    
1745        /**
1746         * Returns the order in which the columns are rendered.  The default value
1747         * is <code>SortOrder.ASCENDING</code>.
1748         *
1749         * @return The column rendering order (never <code>null</code).
1750         *
1751         * @see #setColumnRenderingOrder(SortOrder)
1752         */
1753        public SortOrder getColumnRenderingOrder() {
1754            return this.columnRenderingOrder;
1755        }
1756    
1757        /**
1758         * Sets the column order in which the items in each dataset should be
1759         * rendered and sends a {@link PlotChangeEvent} to all registered
1760         * listeners.  Note that this affects the order in which items are drawn,
1761         * NOT their position in the chart.
1762         *
1763         * @param order  the order (<code>null</code> not permitted).
1764         *
1765         * @see #getColumnRenderingOrder()
1766         * @see #setRowRenderingOrder(SortOrder)
1767         */
1768        public void setColumnRenderingOrder(SortOrder order) {
1769            if (order == null) {
1770                throw new IllegalArgumentException("Null 'order' argument.");
1771            }
1772            this.columnRenderingOrder = order;
1773            fireChangeEvent();
1774        }
1775    
1776        /**
1777         * Returns the order in which the rows should be rendered.  The default
1778         * value is <code>SortOrder.ASCENDING</code>.
1779         *
1780         * @return The order (never <code>null</code>).
1781         *
1782         * @see #setRowRenderingOrder(SortOrder)
1783         */
1784        public SortOrder getRowRenderingOrder() {
1785            return this.rowRenderingOrder;
1786        }
1787    
1788        /**
1789         * Sets the row order in which the items in each dataset should be
1790         * rendered and sends a {@link PlotChangeEvent} to all registered
1791         * listeners.  Note that this affects the order in which items are drawn,
1792         * NOT their position in the chart.
1793         *
1794         * @param order  the order (<code>null</code> not permitted).
1795         *
1796         * @see #getRowRenderingOrder()
1797         * @see #setColumnRenderingOrder(SortOrder)
1798         */
1799        public void setRowRenderingOrder(SortOrder order) {
1800            if (order == null) {
1801                throw new IllegalArgumentException("Null 'order' argument.");
1802            }
1803            this.rowRenderingOrder = order;
1804            fireChangeEvent();
1805        }
1806    
1807        /**
1808         * Returns the flag that controls whether the domain grid-lines are visible.
1809         *
1810         * @return The <code>true</code> or <code>false</code>.
1811         *
1812         * @see #setDomainGridlinesVisible(boolean)
1813         */
1814        public boolean isDomainGridlinesVisible() {
1815            return this.domainGridlinesVisible;
1816        }
1817    
1818        /**
1819         * Sets the flag that controls whether or not grid-lines are drawn against
1820         * the domain axis.
1821         * <p>
1822         * If the flag value changes, a {@link PlotChangeEvent} is sent to all
1823         * registered listeners.
1824         *
1825         * @param visible  the new value of the flag.
1826         *
1827         * @see #isDomainGridlinesVisible()
1828         */
1829        public void setDomainGridlinesVisible(boolean visible) {
1830            if (this.domainGridlinesVisible != visible) {
1831                this.domainGridlinesVisible = visible;
1832                fireChangeEvent();
1833            }
1834        }
1835    
1836        /**
1837         * Returns the position used for the domain gridlines.
1838         *
1839         * @return The gridline position (never <code>null</code>).
1840         *
1841         * @see #setDomainGridlinePosition(CategoryAnchor)
1842         */
1843        public CategoryAnchor getDomainGridlinePosition() {
1844            return this.domainGridlinePosition;
1845        }
1846    
1847        /**
1848         * Sets the position used for the domain gridlines and sends a
1849         * {@link PlotChangeEvent} to all registered listeners.
1850         *
1851         * @param position  the position (<code>null</code> not permitted).
1852         *
1853         * @see #getDomainGridlinePosition()
1854         */
1855        public void setDomainGridlinePosition(CategoryAnchor position) {
1856            if (position == null) {
1857                throw new IllegalArgumentException("Null 'position' argument.");
1858            }
1859            this.domainGridlinePosition = position;
1860            fireChangeEvent();
1861        }
1862    
1863        /**
1864         * Returns the stroke used to draw grid-lines against the domain axis.
1865         *
1866         * @return The stroke (never <code>null</code>).
1867         *
1868         * @see #setDomainGridlineStroke(Stroke)
1869         */
1870        public Stroke getDomainGridlineStroke() {
1871            return this.domainGridlineStroke;
1872        }
1873    
1874        /**
1875         * Sets the stroke used to draw grid-lines against the domain axis and
1876         * sends a {@link PlotChangeEvent} to all registered listeners.
1877         *
1878         * @param stroke  the stroke (<code>null</code> not permitted).
1879         *
1880         * @see #getDomainGridlineStroke()
1881         */
1882        public void setDomainGridlineStroke(Stroke stroke) {
1883            if (stroke == null) {
1884                throw new IllegalArgumentException("Null 'stroke' not permitted.");
1885            }
1886            this.domainGridlineStroke = stroke;
1887            fireChangeEvent();
1888        }
1889    
1890        /**
1891         * Returns the paint used to draw grid-lines against the domain axis.
1892         *
1893         * @return The paint (never <code>null</code>).
1894         *
1895         * @see #setDomainGridlinePaint(Paint)
1896         */
1897        public Paint getDomainGridlinePaint() {
1898            return this.domainGridlinePaint;
1899        }
1900    
1901        /**
1902         * Sets the paint used to draw the grid-lines (if any) against the domain
1903         * axis and sends a {@link PlotChangeEvent} to all registered listeners.
1904         *
1905         * @param paint  the paint (<code>null</code> not permitted).
1906         *
1907         * @see #getDomainGridlinePaint()
1908         */
1909        public void setDomainGridlinePaint(Paint paint) {
1910            if (paint == null) {
1911                throw new IllegalArgumentException("Null 'paint' argument.");
1912            }
1913            this.domainGridlinePaint = paint;
1914            fireChangeEvent();
1915        }
1916    
1917        /**
1918         * Returns a flag that controls whether or not a zero baseline is
1919         * displayed for the range axis.
1920         *
1921         * @return A boolean.
1922         *
1923         * @see #setRangeZeroBaselineVisible(boolean)
1924         *
1925         * @since 1.0.13
1926         */
1927        public boolean isRangeZeroBaselineVisible() {
1928            return this.rangeZeroBaselineVisible;
1929        }
1930    
1931        /**
1932         * Sets the flag that controls whether or not the zero baseline is
1933         * displayed for the range axis, and sends a {@link PlotChangeEvent} to
1934         * all registered listeners.
1935         *
1936         * @param visible  the flag.
1937         *
1938         * @see #isRangeZeroBaselineVisible()
1939         *
1940         * @since 1.0.13
1941         */
1942        public void setRangeZeroBaselineVisible(boolean visible) {
1943            this.rangeZeroBaselineVisible = visible;
1944            fireChangeEvent();
1945        }
1946    
1947        /**
1948         * Returns the stroke used for the zero baseline against the range axis.
1949         *
1950         * @return The stroke (never <code>null</code>).
1951         *
1952         * @see #setRangeZeroBaselineStroke(Stroke)
1953         *
1954         * @since 1.0.13
1955         */
1956        public Stroke getRangeZeroBaselineStroke() {
1957            return this.rangeZeroBaselineStroke;
1958        }
1959    
1960        /**
1961         * Sets the stroke for the zero baseline for the range axis,
1962         * and sends a {@link PlotChangeEvent} to all registered listeners.
1963         *
1964         * @param stroke  the stroke (<code>null</code> not permitted).
1965         *
1966         * @see #getRangeZeroBaselineStroke()
1967         *
1968         * @since 1.0.13
1969         */
1970        public void setRangeZeroBaselineStroke(Stroke stroke) {
1971            if (stroke == null) {
1972                throw new IllegalArgumentException("Null 'stroke' argument.");
1973            }
1974            this.rangeZeroBaselineStroke = stroke;
1975            fireChangeEvent();
1976        }
1977    
1978        /**
1979         * Returns the paint for the zero baseline (if any) plotted against the
1980         * range axis.
1981         *
1982         * @return The paint (never <code>null</code>).
1983         *
1984         * @see #setRangeZeroBaselinePaint(Paint)
1985         *
1986         * @since 1.0.13
1987         */
1988        public Paint getRangeZeroBaselinePaint() {
1989            return this.rangeZeroBaselinePaint;
1990        }
1991    
1992        /**
1993         * Sets the paint for the zero baseline plotted against the range axis and
1994         * sends a {@link PlotChangeEvent} to all registered listeners.
1995         *
1996         * @param paint  the paint (<code>null</code> not permitted).
1997         *
1998         * @see #getRangeZeroBaselinePaint()
1999         *
2000         * @since 1.0.13
2001         */
2002        public void setRangeZeroBaselinePaint(Paint paint) {
2003            if (paint == null) {
2004                throw new IllegalArgumentException("Null 'paint' argument.");
2005            }
2006            this.rangeZeroBaselinePaint = paint;
2007            fireChangeEvent();
2008        }
2009    
2010        /**
2011         * Returns the flag that controls whether the range grid-lines are visible.
2012         *
2013         * @return The flag.
2014         *
2015         * @see #setRangeGridlinesVisible(boolean)
2016         */
2017        public boolean isRangeGridlinesVisible() {
2018            return this.rangeGridlinesVisible;
2019        }
2020    
2021        /**
2022         * Sets the flag that controls whether or not grid-lines are drawn against
2023         * the range axis.  If the flag changes value, a {@link PlotChangeEvent} is
2024         * sent to all registered listeners.
2025         *
2026         * @param visible  the new value of the flag.
2027         *
2028         * @see #isRangeGridlinesVisible()
2029         */
2030        public void setRangeGridlinesVisible(boolean visible) {
2031            if (this.rangeGridlinesVisible != visible) {
2032                this.rangeGridlinesVisible = visible;
2033                fireChangeEvent();
2034            }
2035        }
2036    
2037        /**
2038         * Returns the stroke used to draw the grid-lines against the range axis.
2039         *
2040         * @return The stroke (never <code>null</code>).
2041         *
2042         * @see #setRangeGridlineStroke(Stroke)
2043         */
2044        public Stroke getRangeGridlineStroke() {
2045            return this.rangeGridlineStroke;
2046        }
2047    
2048        /**
2049         * Sets the stroke used to draw the grid-lines against the range axis and
2050         * sends a {@link PlotChangeEvent} to all registered listeners.
2051         *
2052         * @param stroke  the stroke (<code>null</code> not permitted).
2053         *
2054         * @see #getRangeGridlineStroke()
2055         */
2056        public void setRangeGridlineStroke(Stroke stroke) {
2057            if (stroke == null) {
2058                throw new IllegalArgumentException("Null 'stroke' argument.");
2059            }
2060            this.rangeGridlineStroke = stroke;
2061            fireChangeEvent();
2062        }
2063    
2064        /**
2065         * Returns the paint used to draw the grid-lines against the range axis.
2066         *
2067         * @return The paint (never <code>null</code>).
2068         *
2069         * @see #setRangeGridlinePaint(Paint)
2070         */
2071        public Paint getRangeGridlinePaint() {
2072            return this.rangeGridlinePaint;
2073        }
2074    
2075        /**
2076         * Sets the paint used to draw the grid lines against the range axis and
2077         * sends a {@link PlotChangeEvent} to all registered listeners.
2078         *
2079         * @param paint  the paint (<code>null</code> not permitted).
2080         *
2081         * @see #getRangeGridlinePaint()
2082         */
2083        public void setRangeGridlinePaint(Paint paint) {
2084            if (paint == null) {
2085                throw new IllegalArgumentException("Null 'paint' argument.");
2086            }
2087            this.rangeGridlinePaint = paint;
2088            fireChangeEvent();
2089        }
2090    
2091        /**
2092         * Returns <code>true</code> if the range axis minor grid is visible, and
2093         * <code>false</code> otherwise.
2094         *
2095         * @return A boolean.
2096         *
2097         * @see #setRangeMinorGridlinesVisible(boolean)
2098         *
2099         * @since 1.0.13
2100         */
2101        public boolean isRangeMinorGridlinesVisible() {
2102            return this.rangeMinorGridlinesVisible;
2103        }
2104    
2105        /**
2106         * Sets the flag that controls whether or not the range axis minor grid
2107         * lines are visible.
2108         * <p>
2109         * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
2110         * registered listeners.
2111         *
2112         * @param visible  the new value of the flag.
2113         *
2114         * @see #isRangeMinorGridlinesVisible()
2115         *
2116         * @since 1.0.13
2117         */
2118        public void setRangeMinorGridlinesVisible(boolean visible) {
2119            if (this.rangeMinorGridlinesVisible != visible) {
2120                this.rangeMinorGridlinesVisible = visible;
2121                fireChangeEvent();
2122            }
2123        }
2124    
2125        /**
2126         * Returns the stroke for the minor grid lines (if any) plotted against the
2127         * range axis.
2128         *
2129         * @return The stroke (never <code>null</code>).
2130         *
2131         * @see #setRangeMinorGridlineStroke(Stroke)
2132         *
2133         * @since 1.0.13
2134         */
2135        public Stroke getRangeMinorGridlineStroke() {
2136            return this.rangeMinorGridlineStroke;
2137        }
2138    
2139        /**
2140         * Sets the stroke for the minor grid lines plotted against the range axis,
2141         * and sends a {@link PlotChangeEvent} to all registered listeners.
2142         *
2143         * @param stroke  the stroke (<code>null</code> not permitted).
2144         *
2145         * @see #getRangeMinorGridlineStroke()
2146         *
2147         * @since 1.0.13
2148         */
2149        public void setRangeMinorGridlineStroke(Stroke stroke) {
2150            if (stroke == null) {
2151                throw new IllegalArgumentException("Null 'stroke' argument.");
2152            }
2153            this.rangeMinorGridlineStroke = stroke;
2154            fireChangeEvent();
2155        }
2156    
2157        /**
2158         * Returns the paint for the minor grid lines (if any) plotted against the
2159         * range axis.
2160         *
2161         * @return The paint (never <code>null</code>).
2162         *
2163         * @see #setRangeMinorGridlinePaint(Paint)
2164         *
2165         * @since 1.0.13
2166         */
2167        public Paint getRangeMinorGridlinePaint() {
2168            return this.rangeMinorGridlinePaint;
2169        }
2170    
2171        /**
2172         * Sets the paint for the minor grid lines plotted against the range axis
2173         * and sends a {@link PlotChangeEvent} to all registered listeners.
2174         *
2175         * @param paint  the paint (<code>null</code> not permitted).
2176         *
2177         * @see #getRangeMinorGridlinePaint()
2178         *
2179         * @since 1.0.13
2180         */
2181        public void setRangeMinorGridlinePaint(Paint paint) {
2182            if (paint == null) {
2183                throw new IllegalArgumentException("Null 'paint' argument.");
2184            }
2185            this.rangeMinorGridlinePaint = paint;
2186            fireChangeEvent();
2187        }
2188    
2189        /**
2190         * Returns the fixed legend items, if any.
2191         *
2192         * @return The legend items (possibly <code>null</code>).
2193         *
2194         * @see #setFixedLegendItems(LegendItemCollection)
2195         */
2196        public LegendItemCollection getFixedLegendItems() {
2197            return this.fixedLegendItems;
2198        }
2199    
2200        /**
2201         * Sets the fixed legend items for the plot.  Leave this set to
2202         * <code>null</code> if you prefer the legend items to be created
2203         * automatically.
2204         *
2205         * @param items  the legend items (<code>null</code> permitted).
2206         *
2207         * @see #getFixedLegendItems()
2208         */
2209        public void setFixedLegendItems(LegendItemCollection items) {
2210            this.fixedLegendItems = items;
2211            fireChangeEvent();
2212        }
2213    
2214        /**
2215         * Returns the legend items for the plot.  By default, this method creates
2216         * a legend item for each series in each of the datasets.  You can change
2217         * this behaviour by overriding this method.
2218         *
2219         * @return The legend items.
2220         */
2221        public LegendItemCollection getLegendItems() {
2222            if (this.fixedLegendItems != null) {
2223                return this.fixedLegendItems;
2224            }
2225            LegendItemCollection result = new LegendItemCollection();
2226            // get the legend items for the datasets...
2227            int count = this.datasets.size();
2228            for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
2229                CategoryDataset dataset = getDataset(datasetIndex);
2230                if (dataset != null) {
2231                    CategoryItemRenderer renderer = getRenderer(datasetIndex);
2232                    if (renderer != null) {
2233                        result.addAll(renderer.getLegendItems());
2234                    }
2235                }
2236            }
2237            return result;
2238        }
2239    
2240        /**
2241         * Handles a 'click' on the plot by updating the anchor value.
2242         *
2243         * @param x  x-coordinate of the click (in Java2D space).
2244         * @param y  y-coordinate of the click (in Java2D space).
2245         * @param info  information about the plot's dimensions.
2246         *
2247         */
2248        public void handleClick(int x, int y, PlotRenderingInfo info) {
2249    
2250            Rectangle2D dataArea = info.getDataArea();
2251            if (dataArea.contains(x, y)) {
2252                // set the anchor value for the range axis...
2253                double java2D = 0.0;
2254                if (this.orientation == PlotOrientation.HORIZONTAL) {
2255                    java2D = x;
2256                }
2257                else if (this.orientation == PlotOrientation.VERTICAL) {
2258                    java2D = y;
2259                }
2260                RectangleEdge edge = Plot.resolveRangeAxisLocation(
2261                        getRangeAxisLocation(), this.orientation);
2262                double value = getRangeAxis().java2DToValue(
2263                        java2D, info.getDataArea(), edge);
2264                setAnchorValue(value);
2265                setRangeCrosshairValue(value);
2266            }
2267    
2268        }
2269    
2270        /**
2271         * Zooms (in or out) on the plot's value axis.
2272         * <p>
2273         * If the value 0.0 is passed in as the zoom percent, the auto-range
2274         * calculation for the axis is restored (which sets the range to include
2275         * the minimum and maximum data values, thus displaying all the data).
2276         *
2277         * @param percent  the zoom amount.
2278         */
2279        public void zoom(double percent) {
2280    
2281            if (percent > 0.0) {
2282                double range = getRangeAxis().getRange().getLength();
2283                double scaledRange = range * percent;
2284                getRangeAxis().setRange(this.anchorValue - scaledRange / 2.0,
2285                        this.anchorValue + scaledRange / 2.0);
2286            }
2287            else {
2288                getRangeAxis().setAutoRange(true);
2289            }
2290    
2291        }
2292    
2293        /**
2294         * Receives notification of a change to an {@link Annotation} added to
2295         * this plot.
2296         *
2297         * @param event  information about the event (not used here).
2298         *
2299         * @since 1.0.14
2300         */
2301        public void annotationChanged(AnnotationChangeEvent event) {
2302            if (getParent() != null) {
2303                getParent().annotationChanged(event);
2304            }
2305            else {
2306                PlotChangeEvent e = new PlotChangeEvent(this);
2307                notifyListeners(e);
2308            }
2309        }
2310    
2311        /**
2312         * Receives notification of a change to the plot's dataset.
2313         * <P>
2314         * The range axis bounds will be recalculated if necessary.
2315         *
2316         * @param event  information about the event (not used here).
2317         */
2318        public void datasetChanged(DatasetChangeEvent event) {
2319    
2320            int count = this.rangeAxes.size();
2321            for (int axisIndex = 0; axisIndex < count; axisIndex++) {
2322                ValueAxis yAxis = getRangeAxis(axisIndex);
2323                if (yAxis != null) {
2324                    yAxis.configure();
2325                }
2326            }
2327            if (getParent() != null) {
2328                getParent().datasetChanged(event);
2329            }
2330            else {
2331                PlotChangeEvent e = new PlotChangeEvent(this);
2332                e.setType(ChartChangeEventType.DATASET_UPDATED);
2333                notifyListeners(e);
2334            }
2335    
2336        }
2337    
2338        /**
2339         * Receives notification of a renderer change event.
2340         *
2341         * @param event  the event.
2342         */
2343        public void rendererChanged(RendererChangeEvent event) {
2344            Plot parent = getParent();
2345            if (parent != null) {
2346                if (parent instanceof RendererChangeListener) {
2347                    RendererChangeListener rcl = (RendererChangeListener) parent;
2348                    rcl.rendererChanged(event);
2349                }
2350                else {
2351                    // this should never happen with the existing code, but throw
2352                    // an exception in case future changes make it possible...
2353                    throw new RuntimeException(
2354                        "The renderer has changed and I don't know what to do!");
2355                }
2356            }
2357            else {
2358                configureRangeAxes();
2359                PlotChangeEvent e = new PlotChangeEvent(this);
2360                notifyListeners(e);
2361            }
2362        }
2363    
2364        /**
2365         * Adds a marker for display (in the foreground) against the domain axis and
2366         * sends a {@link PlotChangeEvent} to all registered listeners. Typically a
2367         * marker will be drawn by the renderer as a line perpendicular to the
2368         * domain axis, however this is entirely up to the renderer.
2369         *
2370         * @param marker  the marker (<code>null</code> not permitted).
2371         *
2372         * @see #removeDomainMarker(Marker)
2373         */
2374        public void addDomainMarker(CategoryMarker marker) {
2375            addDomainMarker(marker, Layer.FOREGROUND);
2376        }
2377    
2378        /**
2379         * Adds a marker for display against the domain axis and sends a
2380         * {@link PlotChangeEvent} to all registered listeners.  Typically a marker
2381         * will be drawn by the renderer as a line perpendicular to the domain
2382         * axis, however this is entirely up to the renderer.
2383         *
2384         * @param marker  the marker (<code>null</code> not permitted).
2385         * @param layer  the layer (foreground or background) (<code>null</code>
2386         *               not permitted).
2387         *
2388         * @see #removeDomainMarker(Marker, Layer)
2389         */
2390        public void addDomainMarker(CategoryMarker marker, Layer layer) {
2391            addDomainMarker(0, marker, layer);
2392        }
2393    
2394        /**
2395         * Adds a marker for display by a particular renderer and sends a
2396         * {@link PlotChangeEvent} to all registered listeners.
2397         * <P>
2398         * Typically a marker will be drawn by the renderer as a line perpendicular
2399         * to a domain axis, however this is entirely up to the renderer.
2400         *
2401         * @param index  the renderer index.
2402         * @param marker  the marker (<code>null</code> not permitted).
2403         * @param layer  the layer (<code>null</code> not permitted).
2404         *
2405         * @see #removeDomainMarker(int, Marker, Layer)
2406         */
2407        public void addDomainMarker(int index, CategoryMarker marker, Layer layer) {
2408            addDomainMarker(index, marker, layer, true);
2409        }
2410    
2411        /**
2412         * Adds a marker for display by a particular renderer and, if requested,
2413         * sends a {@link PlotChangeEvent} to all registered listeners.
2414         * <P>
2415         * Typically a marker will be drawn by the renderer as a line perpendicular
2416         * to a domain axis, however this is entirely up to the renderer.
2417         *
2418         * @param index  the renderer index.
2419         * @param marker  the marker (<code>null</code> not permitted).
2420         * @param layer  the layer (<code>null</code> not permitted).
2421         * @param notify  notify listeners?
2422         *
2423         * @since 1.0.10
2424         *
2425         * @see #removeDomainMarker(int, Marker, Layer, boolean)
2426         */
2427        public void addDomainMarker(int index, CategoryMarker marker, Layer layer,
2428                boolean notify) {
2429            if (marker == null) {
2430                throw new IllegalArgumentException("Null 'marker' not permitted.");
2431            }
2432            if (layer == null) {
2433                throw new IllegalArgumentException("Null 'layer' not permitted.");
2434            }
2435            Collection markers;
2436            if (layer == Layer.FOREGROUND) {
2437                markers = (Collection) this.foregroundDomainMarkers.get(
2438                        new Integer(index));
2439                if (markers == null) {
2440                    markers = new java.util.ArrayList();
2441                    this.foregroundDomainMarkers.put(new Integer(index), markers);
2442                }
2443                markers.add(marker);
2444            }
2445            else if (layer == Layer.BACKGROUND) {
2446                markers = (Collection) this.backgroundDomainMarkers.get(
2447                        new Integer(index));
2448                if (markers == null) {
2449                    markers = new java.util.ArrayList();
2450                    this.backgroundDomainMarkers.put(new Integer(index), markers);
2451                }
2452                markers.add(marker);
2453            }
2454            marker.addChangeListener(this);
2455            if (notify) {
2456                fireChangeEvent();
2457            }
2458        }
2459    
2460        /**
2461         * Clears all the domain markers for the plot and sends a
2462         * {@link PlotChangeEvent} to all registered listeners.
2463         *
2464         * @see #clearRangeMarkers()
2465         */
2466        public void clearDomainMarkers() {
2467            if (this.backgroundDomainMarkers != null) {
2468                Set keys = this.backgroundDomainMarkers.keySet();
2469                Iterator iterator = keys.iterator();
2470                while (iterator.hasNext()) {
2471                    Integer key = (Integer) iterator.next();
2472                    clearDomainMarkers(key.intValue());
2473                }
2474                this.backgroundDomainMarkers.clear();
2475            }
2476            if (this.foregroundDomainMarkers != null) {
2477                Set keys = this.foregroundDomainMarkers.keySet();
2478                Iterator iterator = keys.iterator();
2479                while (iterator.hasNext()) {
2480                    Integer key = (Integer) iterator.next();
2481                    clearDomainMarkers(key.intValue());
2482                }
2483                this.foregroundDomainMarkers.clear();
2484            }
2485            fireChangeEvent();
2486        }
2487    
2488        /**
2489         * Returns the list of domain markers (read only) for the specified layer.
2490         *
2491         * @param layer  the layer (foreground or background).
2492         *
2493         * @return The list of domain markers.
2494         */
2495        public Collection getDomainMarkers(Layer layer) {
2496            return getDomainMarkers(0, layer);
2497        }
2498    
2499        /**
2500         * Returns a collection of domain markers for a particular renderer and
2501         * layer.
2502         *
2503         * @param index  the renderer index.
2504         * @param layer  the layer.
2505         *
2506         * @return A collection of markers (possibly <code>null</code>).
2507         */
2508        public Collection getDomainMarkers(int index, Layer layer) {
2509            Collection result = null;
2510            Integer key = new Integer(index);
2511            if (layer == Layer.FOREGROUND) {
2512                result = (Collection) this.foregroundDomainMarkers.get(key);
2513            }
2514            else if (layer == Layer.BACKGROUND) {
2515                result = (Collection) this.backgroundDomainMarkers.get(key);
2516            }
2517            if (result != null) {
2518                result = Collections.unmodifiableCollection(result);
2519            }
2520            return result;
2521        }
2522    
2523        /**
2524         * Clears all the domain markers for the specified renderer.
2525         *
2526         * @param index  the renderer index.
2527         *
2528         * @see #clearRangeMarkers(int)
2529         */
2530        public void clearDomainMarkers(int index) {
2531            Integer key = new Integer(index);
2532            if (this.backgroundDomainMarkers != null) {
2533                Collection markers
2534                    = (Collection) this.backgroundDomainMarkers.get(key);
2535                if (markers != null) {
2536                    Iterator iterator = markers.iterator();
2537                    while (iterator.hasNext()) {
2538                        Marker m = (Marker) iterator.next();
2539                        m.removeChangeListener(this);
2540                    }
2541                    markers.clear();
2542                }
2543            }
2544            if (this.foregroundDomainMarkers != null) {
2545                Collection markers
2546                    = (Collection) this.foregroundDomainMarkers.get(key);
2547                if (markers != null) {
2548                    Iterator iterator = markers.iterator();
2549                    while (iterator.hasNext()) {
2550                        Marker m = (Marker) iterator.next();
2551                        m.removeChangeListener(this);
2552                    }
2553                    markers.clear();
2554                }
2555            }
2556            fireChangeEvent();
2557        }
2558    
2559        /**
2560         * Removes a marker for the domain axis and sends a {@link PlotChangeEvent}
2561         * to all registered listeners.
2562         *
2563         * @param marker  the marker.
2564         *
2565         * @return A boolean indicating whether or not the marker was actually
2566         *         removed.
2567         *
2568         * @since 1.0.7
2569         */
2570        public boolean removeDomainMarker(Marker marker) {
2571            return removeDomainMarker(marker, Layer.FOREGROUND);
2572        }
2573    
2574        /**
2575         * Removes a marker for the domain axis in the specified layer and sends a
2576         * {@link PlotChangeEvent} to all registered listeners.
2577         *
2578         * @param marker the marker (<code>null</code> not permitted).
2579         * @param layer the layer (foreground or background).
2580         *
2581         * @return A boolean indicating whether or not the marker was actually
2582         *         removed.
2583         *
2584         * @since 1.0.7
2585         */
2586        public boolean removeDomainMarker(Marker marker, Layer layer) {
2587            return removeDomainMarker(0, marker, layer);
2588        }
2589    
2590        /**
2591         * Removes a marker for a specific dataset/renderer and sends a
2592         * {@link PlotChangeEvent} to all registered listeners.
2593         *
2594         * @param index the dataset/renderer index.
2595         * @param marker the marker.
2596         * @param layer the layer (foreground or background).
2597         *
2598         * @return A boolean indicating whether or not the marker was actually
2599         *         removed.
2600         *
2601         * @since 1.0.7
2602         */
2603        public boolean removeDomainMarker(int index, Marker marker, Layer layer) {
2604            return removeDomainMarker(index, marker, layer, true);
2605        }
2606    
2607        /**
2608         * Removes a marker for a specific dataset/renderer and, if requested,
2609         * sends a {@link PlotChangeEvent} to all registered listeners.
2610         *
2611         * @param index the dataset/renderer index.
2612         * @param marker the marker.
2613         * @param layer the layer (foreground or background).
2614         * @param notify  notify listeners?
2615         *
2616         * @return A boolean indicating whether or not the marker was actually
2617         *         removed.
2618         *
2619         * @since 1.0.10
2620         */
2621        public boolean removeDomainMarker(int index, Marker marker, Layer layer,
2622                boolean notify) {
2623            ArrayList markers;
2624            if (layer == Layer.FOREGROUND) {
2625                markers = (ArrayList) this.foregroundDomainMarkers.get(new Integer(
2626                        index));
2627            }
2628            else {
2629                markers = (ArrayList) this.backgroundDomainMarkers.get(new Integer(
2630                        index));
2631            }
2632            if (markers == null) {
2633                return false;
2634            }
2635            boolean removed = markers.remove(marker);
2636            if (removed && notify) {
2637                fireChangeEvent();
2638            }
2639            return removed;
2640        }
2641    
2642        /**
2643         * Adds a marker for display (in the foreground) against the range axis and
2644         * sends a {@link PlotChangeEvent} to all registered listeners. Typically a
2645         * marker will be drawn by the renderer as a line perpendicular to the
2646         * range axis, however this is entirely up to the renderer.
2647         *
2648         * @param marker  the marker (<code>null</code> not permitted).
2649         *
2650         * @see #removeRangeMarker(Marker)
2651         */
2652        public void addRangeMarker(Marker marker) {
2653            addRangeMarker(marker, Layer.FOREGROUND);
2654        }
2655    
2656        /**
2657         * Adds a marker for display against the range axis and sends a
2658         * {@link PlotChangeEvent} to all registered listeners.  Typically a marker
2659         * will be drawn by the renderer as a line perpendicular to the range axis,
2660         * however this is entirely up to the renderer.
2661         *
2662         * @param marker  the marker (<code>null</code> not permitted).
2663         * @param layer  the layer (foreground or background) (<code>null</code>
2664         *               not permitted).
2665         *
2666         * @see #removeRangeMarker(Marker, Layer)
2667         */
2668        public void addRangeMarker(Marker marker, Layer layer) {
2669            addRangeMarker(0, marker, layer);
2670        }
2671    
2672        /**
2673         * Adds a marker for display by a particular renderer and sends a
2674         * {@link PlotChangeEvent} to all registered listeners.
2675         * <P>
2676         * Typically a marker will be drawn by the renderer as a line perpendicular
2677         * to a range axis, however this is entirely up to the renderer.
2678         *
2679         * @param index  the renderer index.
2680         * @param marker  the marker.
2681         * @param layer  the layer.
2682         *
2683         * @see #removeRangeMarker(int, Marker, Layer)
2684         */
2685        public void addRangeMarker(int index, Marker marker, Layer layer) {
2686            addRangeMarker(index, marker, layer, true);
2687        }
2688    
2689        /**
2690         * Adds a marker for display by a particular renderer and sends a
2691         * {@link PlotChangeEvent} to all registered listeners.
2692         * <P>
2693         * Typically a marker will be drawn by the renderer as a line perpendicular
2694         * to a range axis, however this is entirely up to the renderer.
2695         *
2696         * @param index  the renderer index.
2697         * @param marker  the marker.
2698         * @param layer  the layer.
2699         * @param notify  notify listeners?
2700         *
2701         * @since 1.0.10
2702         *
2703         * @see #removeRangeMarker(int, Marker, Layer, boolean)
2704         */
2705        public void addRangeMarker(int index, Marker marker, Layer layer,
2706                boolean notify) {
2707            Collection markers;
2708            if (layer == Layer.FOREGROUND) {
2709                markers = (Collection) this.foregroundRangeMarkers.get(
2710                        new Integer(index));
2711                if (markers == null) {
2712                    markers = new java.util.ArrayList();
2713                    this.foregroundRangeMarkers.put(new Integer(index), markers);
2714                }
2715                markers.add(marker);
2716            }
2717            else if (layer == Layer.BACKGROUND) {
2718                markers = (Collection) this.backgroundRangeMarkers.get(
2719                        new Integer(index));
2720                if (markers == null) {
2721                    markers = new java.util.ArrayList();
2722                    this.backgroundRangeMarkers.put(new Integer(index), markers);
2723                }
2724                markers.add(marker);
2725            }
2726            marker.addChangeListener(this);
2727            if (notify) {
2728                fireChangeEvent();
2729            }
2730        }
2731    
2732        /**
2733         * Clears all the range markers for the plot and sends a
2734         * {@link PlotChangeEvent} to all registered listeners.
2735         *
2736         * @see #clearDomainMarkers()
2737         */
2738        public void clearRangeMarkers() {
2739            if (this.backgroundRangeMarkers != null) {
2740                Set keys = this.backgroundRangeMarkers.keySet();
2741                Iterator iterator = keys.iterator();
2742                while (iterator.hasNext()) {
2743                    Integer key = (Integer) iterator.next();
2744                    clearRangeMarkers(key.intValue());
2745                }
2746                this.backgroundRangeMarkers.clear();
2747            }
2748            if (this.foregroundRangeMarkers != null) {
2749                Set keys = this.foregroundRangeMarkers.keySet();
2750                Iterator iterator = keys.iterator();
2751                while (iterator.hasNext()) {
2752                    Integer key = (Integer) iterator.next();
2753                    clearRangeMarkers(key.intValue());
2754                }
2755                this.foregroundRangeMarkers.clear();
2756            }
2757            fireChangeEvent();
2758        }
2759    
2760        /**
2761         * Returns the list of range markers (read only) for the specified layer.
2762         *
2763         * @param layer  the layer (foreground or background).
2764         *
2765         * @return The list of range markers.
2766         *
2767         * @see #getRangeMarkers(int, Layer)
2768         */
2769        public Collection getRangeMarkers(Layer layer) {
2770            return getRangeMarkers(0, layer);
2771        }
2772    
2773        /**
2774         * Returns a collection of range markers for a particular renderer and
2775         * layer.
2776         *
2777         * @param index  the renderer index.
2778         * @param layer  the layer.
2779         *
2780         * @return A collection of markers (possibly <code>null</code>).
2781         */
2782        public Collection getRangeMarkers(int index, Layer layer) {
2783            Collection result = null;
2784            Integer key = new Integer(index);
2785            if (layer == Layer.FOREGROUND) {
2786                result = (Collection) this.foregroundRangeMarkers.get(key);
2787            }
2788            else if (layer == Layer.BACKGROUND) {
2789                result = (Collection) this.backgroundRangeMarkers.get(key);
2790            }
2791            if (result != null) {
2792                result = Collections.unmodifiableCollection(result);
2793            }
2794            return result;
2795        }
2796    
2797        /**
2798         * Clears all the range markers for the specified renderer.
2799         *
2800         * @param index  the renderer index.
2801         *
2802         * @see #clearDomainMarkers(int)
2803         */
2804        public void clearRangeMarkers(int index) {
2805            Integer key = new Integer(index);
2806            if (this.backgroundRangeMarkers != null) {
2807                Collection markers
2808                    = (Collection) this.backgroundRangeMarkers.get(key);
2809                if (markers != null) {
2810                    Iterator iterator = markers.iterator();
2811                    while (iterator.hasNext()) {
2812                        Marker m = (Marker) iterator.next();
2813                        m.removeChangeListener(this);
2814                    }
2815                    markers.clear();
2816                }
2817            }
2818            if (this.foregroundRangeMarkers != null) {
2819                Collection markers
2820                    = (Collection) this.foregroundRangeMarkers.get(key);
2821                if (markers != null) {
2822                    Iterator iterator = markers.iterator();
2823                    while (iterator.hasNext()) {
2824                        Marker m = (Marker) iterator.next();
2825                        m.removeChangeListener(this);
2826                    }
2827                    markers.clear();
2828                }
2829            }
2830            fireChangeEvent();
2831        }
2832    
2833        /**
2834         * Removes a marker for the range axis and sends a {@link PlotChangeEvent}
2835         * to all registered listeners.
2836         *
2837         * @param marker the marker.
2838         *
2839         * @return A boolean indicating whether or not the marker was actually
2840         *         removed.
2841         *
2842         * @since 1.0.7
2843         *
2844         * @see #addRangeMarker(Marker)
2845         */
2846        public boolean removeRangeMarker(Marker marker) {
2847            return removeRangeMarker(marker, Layer.FOREGROUND);
2848        }
2849    
2850        /**
2851         * Removes a marker for the range axis in the specified layer and sends a
2852         * {@link PlotChangeEvent} to all registered listeners.
2853         *
2854         * @param marker the marker (<code>null</code> not permitted).
2855         * @param layer the layer (foreground or background).
2856         *
2857         * @return A boolean indicating whether or not the marker was actually
2858         *         removed.
2859         *
2860         * @since 1.0.7
2861         *
2862         * @see #addRangeMarker(Marker, Layer)
2863         */
2864        public boolean removeRangeMarker(Marker marker, Layer layer) {
2865            return removeRangeMarker(0, marker, layer);
2866        }
2867    
2868        /**
2869         * Removes a marker for a specific dataset/renderer and sends a
2870         * {@link PlotChangeEvent} to all registered listeners.
2871         *
2872         * @param index the dataset/renderer index.
2873         * @param marker the marker.
2874         * @param layer the layer (foreground or background).
2875         *
2876         * @return A boolean indicating whether or not the marker was actually
2877         *         removed.
2878         *
2879         * @since 1.0.7
2880         *
2881         * @see #addRangeMarker(int, Marker, Layer)
2882         */
2883        public boolean removeRangeMarker(int index, Marker marker, Layer layer) {
2884            return removeRangeMarker(index, marker, layer, true);
2885        }
2886    
2887        /**
2888         * Removes a marker for a specific dataset/renderer and sends a
2889         * {@link PlotChangeEvent} to all registered listeners.
2890         *
2891         * @param index  the dataset/renderer index.
2892         * @param marker  the marker.
2893         * @param layer  the layer (foreground or background).
2894         * @param notify  notify listeners.
2895         *
2896         * @return A boolean indicating whether or not the marker was actually
2897         *         removed.
2898         *
2899         * @since 1.0.10
2900         *
2901         * @see #addRangeMarker(int, Marker, Layer, boolean)
2902         */
2903        public boolean removeRangeMarker(int index, Marker marker, Layer layer,
2904                boolean notify) {
2905            if (marker == null) {
2906                throw new IllegalArgumentException("Null 'marker' argument.");
2907            }
2908            ArrayList markers;
2909            if (layer == Layer.FOREGROUND) {
2910                markers = (ArrayList) this.foregroundRangeMarkers.get(new Integer(
2911                        index));
2912            }
2913            else {
2914                markers = (ArrayList) this.backgroundRangeMarkers.get(new Integer(
2915                        index));
2916            }
2917            if (markers == null) {
2918                return false;
2919            }
2920            boolean removed = markers.remove(marker);
2921            if (removed && notify) {
2922                fireChangeEvent();
2923            }
2924            return removed;
2925        }
2926    
2927        /**
2928         * Returns the flag that controls whether or not the domain crosshair is
2929         * displayed by the plot.
2930         *
2931         * @return A boolean.
2932         *
2933         * @since 1.0.11
2934         *
2935         * @see #setDomainCrosshairVisible(boolean)
2936         */
2937        public boolean isDomainCrosshairVisible() {
2938            return this.domainCrosshairVisible;
2939        }
2940    
2941        /**
2942         * Sets the flag that controls whether or not the domain crosshair is
2943         * displayed by the plot, and sends a {@link PlotChangeEvent} to all
2944         * registered listeners.
2945         *
2946         * @param flag  the new flag value.
2947         *
2948         * @since 1.0.11
2949         *
2950         * @see #isDomainCrosshairVisible()
2951         * @see #setRangeCrosshairVisible(boolean)
2952         */
2953        public void setDomainCrosshairVisible(boolean flag) {
2954            if (this.domainCrosshairVisible != flag) {
2955                this.domainCrosshairVisible = flag;
2956                fireChangeEvent();
2957            }
2958        }
2959    
2960        /**
2961         * Returns the row key for the domain crosshair.
2962         *
2963         * @return The row key.
2964         *
2965         * @since 1.0.11
2966         */
2967        public Comparable getDomainCrosshairRowKey() {
2968            return this.domainCrosshairRowKey;
2969        }
2970    
2971        /**
2972         * Sets the row key for the domain crosshair and sends a
2973         * {PlotChangeEvent} to all registered listeners.
2974         *
2975         * @param key  the key.
2976         *
2977         * @since 1.0.11
2978         */
2979        public void setDomainCrosshairRowKey(Comparable key) {
2980            setDomainCrosshairRowKey(key, true);
2981        }
2982    
2983        /**
2984         * Sets the row key for the domain crosshair and, if requested, sends a
2985         * {PlotChangeEvent} to all registered listeners.
2986         *
2987         * @param key  the key.
2988         * @param notify  notify listeners?
2989         *
2990         * @since 1.0.11
2991         */
2992        public void setDomainCrosshairRowKey(Comparable key, boolean notify) {
2993            this.domainCrosshairRowKey = key;
2994            if (notify) {
2995                fireChangeEvent();
2996            }
2997        }
2998    
2999        /**
3000         * Returns the column key for the domain crosshair.
3001         *
3002         * @return The column key.
3003         *
3004         * @since 1.0.11
3005         */
3006        public Comparable getDomainCrosshairColumnKey() {
3007            return this.domainCrosshairColumnKey;
3008        }
3009    
3010        /**
3011         * Sets the column key for the domain crosshair and sends
3012         * a {@link PlotChangeEvent} to all registered listeners.
3013         *
3014         * @param key  the key.
3015         *
3016         * @since 1.0.11
3017         */
3018        public void setDomainCrosshairColumnKey(Comparable key) {
3019            setDomainCrosshairColumnKey(key, true);
3020        }
3021    
3022        /**
3023         * Sets the column key for the domain crosshair and, if requested, sends
3024         * a {@link PlotChangeEvent} to all registered listeners.
3025         *
3026         * @param key  the key.
3027         * @param notify  notify listeners?
3028         *
3029         * @since 1.0.11
3030         */
3031        public void setDomainCrosshairColumnKey(Comparable key, boolean notify) {
3032            this.domainCrosshairColumnKey = key;
3033            if (notify) {
3034                fireChangeEvent();
3035            }
3036        }
3037    
3038        /**
3039         * Returns the dataset index for the crosshair.
3040         *
3041         * @return The dataset index.
3042         *
3043         * @since 1.0.11
3044         */
3045        public int getCrosshairDatasetIndex() {
3046            return this.crosshairDatasetIndex;
3047        }
3048    
3049        /**
3050         * Sets the dataset index for the crosshair and sends a
3051         * {@link PlotChangeEvent} to all registered listeners.
3052         *
3053         * @param index  the index.
3054         *
3055         * @since 1.0.11
3056         */
3057        public void setCrosshairDatasetIndex(int index) {
3058            setCrosshairDatasetIndex(index, true);
3059        }
3060    
3061        /**
3062         * Sets the dataset index for the crosshair and, if requested, sends a
3063         * {@link PlotChangeEvent} to all registered listeners.
3064         *
3065         * @param index  the index.
3066         * @param notify  notify listeners?
3067         *
3068         * @since 1.0.11
3069         */
3070        public void setCrosshairDatasetIndex(int index, boolean notify) {
3071            this.crosshairDatasetIndex = index;
3072            if (notify) {
3073                fireChangeEvent();
3074            }
3075        }
3076    
3077        /**
3078         * Returns the paint used to draw the domain crosshair.
3079         *
3080         * @return The paint (never <code>null</code>).
3081         *
3082         * @since 1.0.11
3083         *
3084         * @see #setDomainCrosshairPaint(Paint)
3085         * @see #getDomainCrosshairStroke()
3086         */
3087        public Paint getDomainCrosshairPaint() {
3088            return this.domainCrosshairPaint;
3089        }
3090    
3091        /**
3092         * Sets the paint used to draw the domain crosshair.
3093         *
3094         * @param paint  the paint (<code>null</code> not permitted).
3095         *
3096         * @since 1.0.11
3097         *
3098         * @see #getDomainCrosshairPaint()
3099         */
3100        public void setDomainCrosshairPaint(Paint paint) {
3101            if (paint == null) {
3102                throw new IllegalArgumentException("Null 'paint' argument.");
3103            }
3104            this.domainCrosshairPaint = paint;
3105            fireChangeEvent();
3106        }
3107    
3108        /**
3109         * Returns the stroke used to draw the domain crosshair.
3110         *
3111         * @return The stroke (never <code>null</code>).
3112         *
3113         * @since 1.0.11
3114         *
3115         * @see #setDomainCrosshairStroke(Stroke)
3116         * @see #getDomainCrosshairPaint()
3117         */
3118        public Stroke getDomainCrosshairStroke() {
3119            return this.domainCrosshairStroke;
3120        }
3121    
3122        /**
3123         * Sets the stroke used to draw the domain crosshair, and sends a
3124         * {@link PlotChangeEvent} to all registered listeners.
3125         *
3126         * @param stroke  the stroke (<code>null</code> not permitted).
3127         *
3128         * @since 1.0.11
3129         *
3130         * @see #getDomainCrosshairStroke()
3131         */
3132        public void setDomainCrosshairStroke(Stroke stroke) {
3133            if (stroke == null) {
3134                throw new IllegalArgumentException("Null 'stroke' argument.");
3135            }
3136            this.domainCrosshairStroke = stroke;
3137        }
3138    
3139        /**
3140         * Returns a flag indicating whether or not the range crosshair is visible.
3141         *
3142         * @return The flag.
3143         *
3144         * @see #setRangeCrosshairVisible(boolean)
3145         */
3146        public boolean isRangeCrosshairVisible() {
3147            return this.rangeCrosshairVisible;
3148        }
3149    
3150        /**
3151         * Sets the flag indicating whether or not the range crosshair is visible.
3152         *
3153         * @param flag  the new value of the flag.
3154         *
3155         * @see #isRangeCrosshairVisible()
3156         */
3157        public void setRangeCrosshairVisible(boolean flag) {
3158            if (this.rangeCrosshairVisible != flag) {
3159                this.rangeCrosshairVisible = flag;
3160                fireChangeEvent();
3161            }
3162        }
3163    
3164        /**
3165         * Returns a flag indicating whether or not the crosshair should "lock-on"
3166         * to actual data values.
3167         *
3168         * @return The flag.
3169         *
3170         * @see #setRangeCrosshairLockedOnData(boolean)
3171         */
3172        public boolean isRangeCrosshairLockedOnData() {
3173            return this.rangeCrosshairLockedOnData;
3174        }
3175    
3176        /**
3177         * Sets the flag indicating whether or not the range crosshair should
3178         * "lock-on" to actual data values, and sends a {@link PlotChangeEvent}
3179         * to all registered listeners.
3180         *
3181         * @param flag  the flag.
3182         *
3183         * @see #isRangeCrosshairLockedOnData()
3184         */
3185        public void setRangeCrosshairLockedOnData(boolean flag) {
3186            if (this.rangeCrosshairLockedOnData != flag) {
3187                this.rangeCrosshairLockedOnData = flag;
3188                fireChangeEvent();
3189            }
3190        }
3191    
3192        /**
3193         * Returns the range crosshair value.
3194         *
3195         * @return The value.
3196         *
3197         * @see #setRangeCrosshairValue(double)
3198         */
3199        public double getRangeCrosshairValue() {
3200            return this.rangeCrosshairValue;
3201        }
3202    
3203        /**
3204         * Sets the range crosshair value and, if the crosshair is visible, sends
3205         * a {@link PlotChangeEvent} to all registered listeners.
3206         *
3207         * @param value  the new value.
3208         *
3209         * @see #getRangeCrosshairValue()
3210         */
3211        public void setRangeCrosshairValue(double value) {
3212            setRangeCrosshairValue(value, true);
3213        }
3214    
3215        /**
3216         * Sets the range crosshair value and, if requested, sends a
3217         * {@link PlotChangeEvent} to all registered listeners (but only if the
3218         * crosshair is visible).
3219         *
3220         * @param value  the new value.
3221         * @param notify  a flag that controls whether or not listeners are
3222         *                notified.
3223         *
3224         * @see #getRangeCrosshairValue()
3225         */
3226        public void setRangeCrosshairValue(double value, boolean notify) {
3227            this.rangeCrosshairValue = value;
3228            if (isRangeCrosshairVisible() && notify) {
3229                fireChangeEvent();
3230            }
3231        }
3232    
3233        /**
3234         * Returns the pen-style (<code>Stroke</code>) used to draw the crosshair
3235         * (if visible).
3236         *
3237         * @return The crosshair stroke (never <code>null</code>).
3238         *
3239         * @see #setRangeCrosshairStroke(Stroke)
3240         * @see #isRangeCrosshairVisible()
3241         * @see #getRangeCrosshairPaint()
3242         */
3243        public Stroke getRangeCrosshairStroke() {
3244            return this.rangeCrosshairStroke;
3245        }
3246    
3247        /**
3248         * Sets the pen-style (<code>Stroke</code>) used to draw the range
3249         * crosshair (if visible), and sends a {@link PlotChangeEvent} to all
3250         * registered listeners.
3251         *
3252         * @param stroke  the new crosshair stroke (<code>null</code> not
3253         *         permitted).
3254         *
3255         * @see #getRangeCrosshairStroke()
3256         */
3257        public void setRangeCrosshairStroke(Stroke stroke) {
3258            if (stroke == null) {
3259                throw new IllegalArgumentException("Null 'stroke' argument.");
3260            }
3261            this.rangeCrosshairStroke = stroke;
3262            fireChangeEvent();
3263        }
3264    
3265        /**
3266         * Returns the paint used to draw the range crosshair.
3267         *
3268         * @return The paint (never <code>null</code>).
3269         *
3270         * @see #setRangeCrosshairPaint(Paint)
3271         * @see #isRangeCrosshairVisible()
3272         * @see #getRangeCrosshairStroke()
3273         */
3274        public Paint getRangeCrosshairPaint() {
3275            return this.rangeCrosshairPaint;
3276        }
3277    
3278        /**
3279         * Sets the paint used to draw the range crosshair (if visible) and
3280         * sends a {@link PlotChangeEvent} to all registered listeners.
3281         *
3282         * @param paint  the paint (<code>null</code> not permitted).
3283         *
3284         * @see #getRangeCrosshairPaint()
3285         */
3286        public void setRangeCrosshairPaint(Paint paint) {
3287            if (paint == null) {
3288                throw new IllegalArgumentException("Null 'paint' argument.");
3289            }
3290            this.rangeCrosshairPaint = paint;
3291            fireChangeEvent();
3292        }
3293    
3294        /**
3295         * Returns the list of annotations.
3296         *
3297         * @return The list of annotations (never <code>null</code>).
3298         *
3299         * @see #addAnnotation(CategoryAnnotation)
3300         * @see #clearAnnotations()
3301         */
3302        public List getAnnotations() {
3303            return this.annotations;
3304        }
3305    
3306        /**
3307         * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to all
3308         * registered listeners.
3309         *
3310         * @param annotation  the annotation (<code>null</code> not permitted).
3311         *
3312         * @see #removeAnnotation(CategoryAnnotation)
3313         */
3314        public void addAnnotation(CategoryAnnotation annotation) {
3315            addAnnotation(annotation, true);
3316        }
3317    
3318        /**
3319         * Adds an annotation to the plot and, if requested, sends a
3320         * {@link PlotChangeEvent} to all registered listeners.
3321         *
3322         * @param annotation  the annotation (<code>null</code> not permitted).
3323         * @param notify  notify listeners?
3324         *
3325         * @since 1.0.10
3326         */
3327        public void addAnnotation(CategoryAnnotation annotation, boolean notify) {
3328            if (annotation == null) {
3329                throw new IllegalArgumentException("Null 'annotation' argument.");
3330            }
3331            this.annotations.add(annotation);
3332            annotation.addChangeListener(this);
3333            if (notify) {
3334                fireChangeEvent();
3335            }
3336        }
3337    
3338        /**
3339         * Removes an annotation from the plot and sends a {@link PlotChangeEvent}
3340         * to all registered listeners.
3341         *
3342         * @param annotation  the annotation (<code>null</code> not permitted).
3343         *
3344         * @return A boolean (indicates whether or not the annotation was removed).
3345         *
3346         * @see #addAnnotation(CategoryAnnotation)
3347         */
3348        public boolean removeAnnotation(CategoryAnnotation annotation) {
3349            return removeAnnotation(annotation, true);
3350        }
3351    
3352        /**
3353         * Removes an annotation from the plot and, if requested, sends a
3354         * {@link PlotChangeEvent} to all registered listeners.
3355         *
3356         * @param annotation  the annotation (<code>null</code> not permitted).
3357         * @param notify  notify listeners?
3358         *
3359         * @return A boolean (indicates whether or not the annotation was removed).
3360         *
3361         * @since 1.0.10
3362         */
3363        public boolean removeAnnotation(CategoryAnnotation annotation,
3364                boolean notify) {
3365            if (annotation == null) {
3366                throw new IllegalArgumentException("Null 'annotation' argument.");
3367            }
3368            boolean removed = this.annotations.remove(annotation);
3369            annotation.removeChangeListener(this);
3370            if (removed && notify) {
3371                fireChangeEvent();
3372            }
3373            return removed;
3374        }
3375    
3376        /**
3377         * Clears all the annotations and sends a {@link PlotChangeEvent} to all
3378         * registered listeners.
3379         */
3380        public void clearAnnotations() {
3381            for(int i = 0; i < this.annotations.size(); i++) {
3382                CategoryAnnotation annotation 
3383                        = (CategoryAnnotation) this.annotations.get(i);
3384                annotation.removeChangeListener(this);
3385            }
3386            this.annotations.clear();
3387            fireChangeEvent();
3388        }
3389    
3390        /**
3391         * Returns the shadow generator for the plot, if any.
3392         *
3393         * @return The shadow generator (possibly <code>null</code>).
3394         *
3395         * @since 1.0.14
3396         */
3397        public ShadowGenerator getShadowGenerator() {
3398            return this.shadowGenerator;
3399        }
3400    
3401        /**
3402         * Sets the shadow generator for the plot and sends a
3403         * {@link PlotChangeEvent} to all registered listeners.
3404         *
3405         * @param generator  the generator (<code>null</code> permitted).
3406         *
3407         * @since 1.0.14
3408         */
3409        public void setShadowGenerator(ShadowGenerator generator) {
3410            this.shadowGenerator = generator;
3411            fireChangeEvent();
3412        }
3413    
3414        /**
3415         * Calculates the space required for the domain axis/axes.
3416         *
3417         * @param g2  the graphics device.
3418         * @param plotArea  the plot area.
3419         * @param space  a carrier for the result (<code>null</code> permitted).
3420         *
3421         * @return The required space.
3422         */
3423        protected AxisSpace calculateDomainAxisSpace(Graphics2D g2,
3424                                                     Rectangle2D plotArea,
3425                                                     AxisSpace space) {
3426    
3427            if (space == null) {
3428                space = new AxisSpace();
3429            }
3430    
3431            // reserve some space for the domain axis...
3432            if (this.fixedDomainAxisSpace != null) {
3433                if (this.orientation == PlotOrientation.HORIZONTAL) {
3434                    space.ensureAtLeast(
3435                        this.fixedDomainAxisSpace.getLeft(), RectangleEdge.LEFT);
3436                    space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(),
3437                            RectangleEdge.RIGHT);
3438                }
3439                else if (this.orientation == PlotOrientation.VERTICAL) {
3440                    space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(),
3441                            RectangleEdge.TOP);
3442                    space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(),
3443                            RectangleEdge.BOTTOM);
3444                }
3445            }
3446            else {
3447                // reserve space for the primary domain axis...
3448                RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
3449                        getDomainAxisLocation(), this.orientation);
3450                if (this.drawSharedDomainAxis) {
3451                    space = getDomainAxis().reserveSpace(g2, this, plotArea,
3452                            domainEdge, space);
3453                }
3454    
3455                // reserve space for any domain axes...
3456                for (int i = 0; i < this.domainAxes.size(); i++) {
3457                    Axis xAxis = (Axis) this.domainAxes.get(i);
3458                    if (xAxis != null) {
3459                        RectangleEdge edge = getDomainAxisEdge(i);
3460                        space = xAxis.reserveSpace(g2, this, plotArea, edge, space);
3461                    }
3462                }
3463            }
3464    
3465            return space;
3466    
3467        }
3468    
3469        /**
3470         * Calculates the space required for the range axis/axes.
3471         *
3472         * @param g2  the graphics device.
3473         * @param plotArea  the plot area.
3474         * @param space  a carrier for the result (<code>null</code> permitted).
3475         *
3476         * @return The required space.
3477         */
3478        protected AxisSpace calculateRangeAxisSpace(Graphics2D g2,
3479                                                    Rectangle2D plotArea,
3480                                                    AxisSpace space) {
3481    
3482            if (space == null) {
3483                space = new AxisSpace();
3484            }
3485    
3486            // reserve some space for the range axis...
3487            if (this.fixedRangeAxisSpace != null) {
3488                if (this.orientation == PlotOrientation.HORIZONTAL) {
3489                    space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(),
3490                            RectangleEdge.TOP);
3491                    space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(),
3492                            RectangleEdge.BOTTOM);
3493                }
3494                else if (this.orientation == PlotOrientation.VERTICAL) {
3495                    space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(),
3496                            RectangleEdge.LEFT);
3497                    space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(),
3498                            RectangleEdge.RIGHT);
3499                }
3500            }
3501            else {
3502                // reserve space for the range axes (if any)...
3503                for (int i = 0; i < this.rangeAxes.size(); i++) {
3504                    Axis yAxis = (Axis) this.rangeAxes.get(i);
3505                    if (yAxis != null) {
3506                        RectangleEdge edge = getRangeAxisEdge(i);
3507                        space = yAxis.reserveSpace(g2, this, plotArea, edge, space);
3508                    }
3509                }
3510            }
3511            return space;
3512    
3513        }
3514    
3515        /**
3516         * Trims a rectangle to integer coordinates.
3517         *
3518         * @param rect  the incoming rectangle.
3519         *
3520         * @return A rectangle with integer coordinates.
3521         */
3522        private Rectangle integerise(Rectangle2D rect) {
3523            int x0 = (int) Math.ceil(rect.getMinX());
3524            int y0 = (int) Math.ceil(rect.getMinY());
3525            int x1 = (int) Math.floor(rect.getMaxX());
3526            int y1 = (int) Math.floor(rect.getMaxY());
3527            return new Rectangle(x0, y0, (x1 - x0), (y1 - y0));
3528        }
3529    
3530        /**
3531         * Calculates the space required for the axes.
3532         *
3533         * @param g2  the graphics device.
3534         * @param plotArea  the plot area.
3535         *
3536         * @return The space required for the axes.
3537         */
3538        protected AxisSpace calculateAxisSpace(Graphics2D g2,
3539                                               Rectangle2D plotArea) {
3540            AxisSpace space = new AxisSpace();
3541            space = calculateRangeAxisSpace(g2, plotArea, space);
3542            space = calculateDomainAxisSpace(g2, plotArea, space);
3543            return space;
3544        }
3545    
3546        /**
3547         * Draws the plot on a Java 2D graphics device (such as the screen or a
3548         * printer).
3549         * <P>
3550         * At your option, you may supply an instance of {@link PlotRenderingInfo}.
3551         * If you do, it will be populated with information about the drawing,
3552         * including various plot dimensions and tooltip info.
3553         *
3554         * @param g2  the graphics device.
3555         * @param area  the area within which the plot (including axes) should
3556         *              be drawn.
3557         * @param anchor  the anchor point (<code>null</code> permitted).
3558         * @param parentState  the state from the parent plot, if there is one.
3559         * @param state  collects info as the chart is drawn (possibly
3560         *               <code>null</code>).
3561         */
3562        public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor,
3563                PlotState parentState, PlotRenderingInfo state) {
3564    
3565            // if the plot area is too small, just return...
3566            boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
3567            boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
3568            if (b1 || b2) {
3569                return;
3570            }
3571    
3572            // record the plot area...
3573            if (state == null) {
3574                // if the incoming state is null, no information will be passed
3575                // back to the caller - but we create a temporary state to record
3576                // the plot area, since that is used later by the axes
3577                state = new PlotRenderingInfo(null);
3578            }
3579            state.setPlotArea(area);
3580    
3581            // adjust the drawing area for the plot insets (if any)...
3582            RectangleInsets insets = getInsets();
3583            insets.trim(area);
3584    
3585            // calculate the data area...
3586            AxisSpace space = calculateAxisSpace(g2, area);
3587            Rectangle2D dataArea = space.shrink(area, null);
3588            this.axisOffset.trim(dataArea);
3589            dataArea = integerise(dataArea);
3590            if (dataArea.isEmpty()) {
3591                return;
3592            }
3593            state.setDataArea(dataArea);
3594            createAndAddEntity((Rectangle2D) dataArea.clone(), state, null, null);
3595    
3596            // if there is a renderer, it draws the background, otherwise use the
3597            // default background...
3598            if (getRenderer() != null) {
3599                getRenderer().drawBackground(g2, this, dataArea);
3600            }
3601            else {
3602                drawBackground(g2, dataArea);
3603            }
3604    
3605            Map axisStateMap = drawAxes(g2, area, dataArea, state);
3606    
3607            // the anchor point is typically the point where the mouse last
3608            // clicked - the crosshairs will be driven off this point...
3609            if (anchor != null && !dataArea.contains(anchor)) {
3610                anchor = ShapeUtilities.getPointInRectangle(anchor.getX(),
3611                        anchor.getY(), dataArea);
3612            }
3613            CategoryCrosshairState crosshairState = new CategoryCrosshairState();
3614            crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
3615            crosshairState.setAnchor(anchor);
3616    
3617            // specify the anchor X and Y coordinates in Java2D space, for the
3618            // cases where these are not updated during rendering (i.e. no lock
3619            // on data)
3620            crosshairState.setAnchorX(Double.NaN);
3621            crosshairState.setAnchorY(Double.NaN);
3622            if (anchor != null) {
3623                ValueAxis rangeAxis = getRangeAxis();
3624                if (rangeAxis != null) {
3625                    double y;
3626                    if (getOrientation() == PlotOrientation.VERTICAL) {
3627                        y = rangeAxis.java2DToValue(anchor.getY(), dataArea,
3628                                getRangeAxisEdge());
3629                    }
3630                    else {
3631                        y = rangeAxis.java2DToValue(anchor.getX(), dataArea,
3632                                getRangeAxisEdge());
3633                    }
3634                    crosshairState.setAnchorY(y);
3635                }
3636            }
3637            crosshairState.setRowKey(getDomainCrosshairRowKey());
3638            crosshairState.setColumnKey(getDomainCrosshairColumnKey());
3639            crosshairState.setCrosshairY(getRangeCrosshairValue());
3640    
3641            // don't let anyone draw outside the data area
3642            Shape savedClip = g2.getClip();
3643            g2.clip(dataArea);
3644    
3645            drawDomainGridlines(g2, dataArea);
3646    
3647            AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis());
3648            if (rangeAxisState == null) {
3649                if (parentState != null) {
3650                    rangeAxisState = (AxisState) parentState.getSharedAxisStates()
3651                            .get(getRangeAxis());
3652                }
3653            }
3654            if (rangeAxisState != null) {
3655                drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks());
3656                drawZeroRangeBaseline(g2, dataArea);
3657            }
3658    
3659            Graphics2D savedG2 = g2;
3660            BufferedImage dataImage = null;
3661            if (this.shadowGenerator != null) {
3662                dataImage = new BufferedImage((int) dataArea.getWidth(),
3663                        (int)dataArea.getHeight(), BufferedImage.TYPE_INT_ARGB);
3664                g2 = dataImage.createGraphics();
3665                g2.translate(-dataArea.getX(), -dataArea.getY());
3666                g2.setRenderingHints(savedG2.getRenderingHints());
3667            }
3668    
3669            // draw the markers...
3670            for (int i = 0; i < this.renderers.size(); i++) {
3671                drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND);
3672            }
3673            for (int i = 0; i < this.renderers.size(); i++) {
3674                drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND);
3675            }
3676    
3677            // now render data items...
3678            boolean foundData = false;
3679    
3680            // set up the alpha-transparency...
3681            Composite originalComposite = g2.getComposite();
3682            g2.setComposite(AlphaComposite.getInstance(
3683                    AlphaComposite.SRC_OVER, getForegroundAlpha()));
3684    
3685            DatasetRenderingOrder order = getDatasetRenderingOrder();
3686            if (order == DatasetRenderingOrder.FORWARD) {
3687                for (int i = 0; i < this.datasets.size(); i++) {
3688                    foundData = render(g2, dataArea, i, state, crosshairState)
3689                        || foundData;
3690                }
3691            }
3692            else {  // DatasetRenderingOrder.REVERSE
3693                for (int i = this.datasets.size() - 1; i >= 0; i--) {
3694                    foundData = render(g2, dataArea, i, state, crosshairState)
3695                        || foundData;
3696                }
3697            }
3698            // draw the foreground markers...
3699            for (int i = 0; i < this.renderers.size(); i++) {
3700                drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND);
3701            }
3702            for (int i = 0; i < this.renderers.size(); i++) {
3703                drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND);
3704            }
3705    
3706            // draw the annotations (if any)...
3707            drawAnnotations(g2, dataArea);
3708    
3709            if (this.shadowGenerator != null) {
3710                BufferedImage shadowImage = this.shadowGenerator.createDropShadow(
3711                        dataImage);
3712                g2 = savedG2;
3713                g2.drawImage(shadowImage, (int) dataArea.getX() 
3714                        + this.shadowGenerator.calculateOffsetX(),
3715                        (int) dataArea.getY() 
3716                        + this.shadowGenerator.calculateOffsetY(), null);
3717                g2.drawImage(dataImage, (int) dataArea.getX(),
3718                        (int) dataArea.getY(), null);
3719            }
3720            g2.setClip(savedClip);
3721            g2.setComposite(originalComposite);
3722    
3723            if (!foundData) {
3724                drawNoDataMessage(g2, dataArea);
3725            }
3726    
3727            int datasetIndex = crosshairState.getDatasetIndex();
3728            setCrosshairDatasetIndex(datasetIndex, false);
3729    
3730            // draw domain crosshair if required...
3731            Comparable rowKey = crosshairState.getRowKey();
3732            Comparable columnKey = crosshairState.getColumnKey();
3733            setDomainCrosshairRowKey(rowKey, false);
3734            setDomainCrosshairColumnKey(columnKey, false);
3735            if (isDomainCrosshairVisible() && columnKey != null) {
3736                Paint paint = getDomainCrosshairPaint();
3737                Stroke stroke = getDomainCrosshairStroke();
3738                drawDomainCrosshair(g2, dataArea, this.orientation,
3739                        datasetIndex, rowKey, columnKey, stroke, paint);
3740            }
3741    
3742            // draw range crosshair if required...
3743            ValueAxis yAxis = getRangeAxisForDataset(datasetIndex);
3744            RectangleEdge yAxisEdge = getRangeAxisEdge();
3745            if (!this.rangeCrosshairLockedOnData && anchor != null) {
3746                double yy;
3747                if (getOrientation() == PlotOrientation.VERTICAL) {
3748                    yy = yAxis.java2DToValue(anchor.getY(), dataArea, yAxisEdge);
3749                }
3750                else {
3751                    yy = yAxis.java2DToValue(anchor.getX(), dataArea, yAxisEdge);
3752                }
3753                crosshairState.setCrosshairY(yy);
3754            }
3755            setRangeCrosshairValue(crosshairState.getCrosshairY(), false);
3756            if (isRangeCrosshairVisible()) {
3757                double y = getRangeCrosshairValue();
3758                Paint paint = getRangeCrosshairPaint();
3759                Stroke stroke = getRangeCrosshairStroke();
3760                drawRangeCrosshair(g2, dataArea, getOrientation(), y, yAxis,
3761                        stroke, paint);
3762            }
3763    
3764            // draw an outline around the plot area...
3765            if (isOutlineVisible()) {
3766                if (getRenderer() != null) {
3767                    getRenderer().drawOutline(g2, this, dataArea);
3768                }
3769                else {
3770                    drawOutline(g2, dataArea);
3771                }
3772            }
3773    
3774        }
3775    
3776        /**
3777         * Draws the plot background (the background color and/or image).
3778         * <P>
3779         * This method will be called during the chart drawing process and is
3780         * declared public so that it can be accessed by the renderers used by
3781         * certain subclasses.  You shouldn't need to call this method directly.
3782         *
3783         * @param g2  the graphics device.
3784         * @param area  the area within which the plot should be drawn.
3785         */
3786        public void drawBackground(Graphics2D g2, Rectangle2D area) {
3787            fillBackground(g2, area, this.orientation);
3788            drawBackgroundImage(g2, area);
3789        }
3790    
3791        /**
3792         * A utility method for drawing the plot's axes.
3793         *
3794         * @param g2  the graphics device.
3795         * @param plotArea  the plot area.
3796         * @param dataArea  the data area.
3797         * @param plotState  collects information about the plot (<code>null</code>
3798         *                   permitted).
3799         *
3800         * @return A map containing the axis states.
3801         */
3802        protected Map drawAxes(Graphics2D g2,
3803                               Rectangle2D plotArea,
3804                               Rectangle2D dataArea,
3805                               PlotRenderingInfo plotState) {
3806    
3807            AxisCollection axisCollection = new AxisCollection();
3808    
3809            // add domain axes to lists...
3810            for (int index = 0; index < this.domainAxes.size(); index++) {
3811                CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(index);
3812                if (xAxis != null) {
3813                    axisCollection.add(xAxis, getDomainAxisEdge(index));
3814                }
3815            }
3816    
3817            // add range axes to lists...
3818            for (int index = 0; index < this.rangeAxes.size(); index++) {
3819                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
3820                if (yAxis != null) {
3821                    axisCollection.add(yAxis, getRangeAxisEdge(index));
3822                }
3823            }
3824    
3825            Map axisStateMap = new HashMap();
3826    
3827            // draw the top axes
3828            double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
3829                    dataArea.getHeight());
3830            Iterator iterator = axisCollection.getAxesAtTop().iterator();
3831            while (iterator.hasNext()) {
3832                Axis axis = (Axis) iterator.next();
3833                if (axis != null) {
3834                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3835                            RectangleEdge.TOP, plotState);
3836                    cursor = axisState.getCursor();
3837                    axisStateMap.put(axis, axisState);
3838                }
3839            }
3840    
3841            // draw the bottom axes
3842            cursor = dataArea.getMaxY()
3843                     + this.axisOffset.calculateBottomOutset(dataArea.getHeight());
3844            iterator = axisCollection.getAxesAtBottom().iterator();
3845            while (iterator.hasNext()) {
3846                Axis axis = (Axis) iterator.next();
3847                if (axis != null) {
3848                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3849                            RectangleEdge.BOTTOM, plotState);
3850                    cursor = axisState.getCursor();
3851                    axisStateMap.put(axis, axisState);
3852                }
3853            }
3854    
3855            // draw the left axes
3856            cursor = dataArea.getMinX()
3857                     - this.axisOffset.calculateLeftOutset(dataArea.getWidth());
3858            iterator = axisCollection.getAxesAtLeft().iterator();
3859            while (iterator.hasNext()) {
3860                Axis axis = (Axis) iterator.next();
3861                if (axis != null) {
3862                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3863                            RectangleEdge.LEFT, plotState);
3864                    cursor = axisState.getCursor();
3865                    axisStateMap.put(axis, axisState);
3866                }
3867            }
3868    
3869            // draw the right axes
3870            cursor = dataArea.getMaxX()
3871                     + this.axisOffset.calculateRightOutset(dataArea.getWidth());
3872            iterator = axisCollection.getAxesAtRight().iterator();
3873            while (iterator.hasNext()) {
3874                Axis axis = (Axis) iterator.next();
3875                if (axis != null) {
3876                    AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea,
3877                            RectangleEdge.RIGHT, plotState);
3878                    cursor = axisState.getCursor();
3879                    axisStateMap.put(axis, axisState);
3880                }
3881            }
3882    
3883            return axisStateMap;
3884    
3885        }
3886    
3887        /**
3888         * Draws a representation of a dataset within the dataArea region using the
3889         * appropriate renderer.
3890         *
3891         * @param g2  the graphics device.
3892         * @param dataArea  the region in which the data is to be drawn.
3893         * @param index  the dataset and renderer index.
3894         * @param info  an optional object for collection dimension information.
3895         * @param crosshairState  a state object for tracking crosshair info
3896         *        (<code>null</code> permitted).
3897         *
3898         * @return A boolean that indicates whether or not real data was found.
3899         *
3900         * @since 1.0.11
3901         */
3902        public boolean render(Graphics2D g2, Rectangle2D dataArea, int index,
3903                PlotRenderingInfo info, CategoryCrosshairState crosshairState) {
3904    
3905            boolean foundData = false;
3906            CategoryDataset currentDataset = getDataset(index);
3907            CategoryItemRenderer renderer = getRenderer(index);
3908            CategoryAxis domainAxis = getDomainAxisForDataset(index);
3909            ValueAxis rangeAxis = getRangeAxisForDataset(index);
3910            boolean hasData = !DatasetUtilities.isEmptyOrNull(currentDataset);
3911            if (hasData && renderer != null) {
3912    
3913                foundData = true;
3914                CategoryItemRendererState state = renderer.initialise(g2, dataArea,
3915                        this, index, info);
3916                state.setCrosshairState(crosshairState);
3917                int columnCount = currentDataset.getColumnCount();
3918                int rowCount = currentDataset.getRowCount();
3919                int passCount = renderer.getPassCount();
3920                for (int pass = 0; pass < passCount; pass++) {
3921                    if (this.columnRenderingOrder == SortOrder.ASCENDING) {
3922                        for (int column = 0; column < columnCount; column++) {
3923                            if (this.rowRenderingOrder == SortOrder.ASCENDING) {
3924                                for (int row = 0; row < rowCount; row++) {
3925                                    renderer.drawItem(g2, state, dataArea, this,
3926                                            domainAxis, rangeAxis, currentDataset,
3927                                            row, column, pass);
3928                                }
3929                            }
3930                            else {
3931                                for (int row = rowCount - 1; row >= 0; row--) {
3932                                    renderer.drawItem(g2, state, dataArea, this,
3933                                            domainAxis, rangeAxis, currentDataset,
3934                                            row, column, pass);
3935                                }
3936                            }
3937                        }
3938                    }
3939                    else {
3940                        for (int column = columnCount - 1; column >= 0; column--) {
3941                            if (this.rowRenderingOrder == SortOrder.ASCENDING) {
3942                                for (int row = 0; row < rowCount; row++) {
3943                                    renderer.drawItem(g2, state, dataArea, this,
3944                                            domainAxis, rangeAxis, currentDataset,
3945                                            row, column, pass);
3946                                }
3947                            }
3948                            else {
3949                                for (int row = rowCount - 1; row >= 0; row--) {
3950                                    renderer.drawItem(g2, state, dataArea, this,
3951                                            domainAxis, rangeAxis, currentDataset,
3952                                            row, column, pass);
3953                                }
3954                            }
3955                        }
3956                    }
3957                }
3958            }
3959            return foundData;
3960    
3961        }
3962    
3963        /**
3964         * Draws the domain gridlines for the plot, if they are visible.
3965         *
3966         * @param g2  the graphics device.
3967         * @param dataArea  the area inside the axes.
3968         *
3969         * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List)
3970         */
3971        protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea) {
3972    
3973            if (!isDomainGridlinesVisible()) {
3974                return;
3975            }
3976            CategoryAnchor anchor = getDomainGridlinePosition();
3977            RectangleEdge domainAxisEdge = getDomainAxisEdge();
3978            CategoryDataset dataset = getDataset();
3979            if (dataset == null) {
3980                return;
3981            }
3982            CategoryAxis axis = getDomainAxis();
3983            if (axis != null) {
3984                int columnCount = dataset.getColumnCount();
3985                for (int c = 0; c < columnCount; c++) {
3986                    double xx = axis.getCategoryJava2DCoordinate(anchor, c,
3987                            columnCount, dataArea, domainAxisEdge);
3988                    CategoryItemRenderer renderer1 = getRenderer();
3989                    if (renderer1 != null) {
3990                        renderer1.drawDomainGridline(g2, this, dataArea, xx);
3991                    }
3992                }
3993            }
3994        }
3995    
3996        /**
3997         * Draws the range gridlines for the plot, if they are visible.
3998         *
3999         * @param g2  the graphics device.
4000         * @param dataArea  the area inside the axes.
4001         * @param ticks  the ticks.
4002         *
4003         * @see #drawDomainGridlines(Graphics2D, Rectangle2D)
4004         */
4005        protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea,
4006                                          List ticks) {
4007            // draw the range grid lines, if any...
4008            if (!isRangeGridlinesVisible() && !isRangeMinorGridlinesVisible()) {
4009                return;
4010            }
4011            // no axis, no gridlines...
4012            ValueAxis axis = getRangeAxis();
4013            if (axis == null) {
4014                return;
4015            }
4016            // no renderer, no gridlines...
4017            CategoryItemRenderer r = getRenderer();
4018            if (r == null) {
4019                return;
4020            }
4021    
4022            Stroke gridStroke = null;
4023            Paint gridPaint = null;
4024            boolean paintLine = false;
4025            Iterator iterator = ticks.iterator();
4026            while (iterator.hasNext()) {
4027                paintLine = false;
4028                ValueTick tick = (ValueTick) iterator.next();
4029                if ((tick.getTickType() == TickType.MINOR)
4030                        && isRangeMinorGridlinesVisible()) {
4031                    gridStroke = getRangeMinorGridlineStroke();
4032                    gridPaint = getRangeMinorGridlinePaint();
4033                    paintLine = true;
4034                }
4035                else if ((tick.getTickType() == TickType.MAJOR)
4036                        && isRangeGridlinesVisible()) {
4037                    gridStroke = getRangeGridlineStroke();
4038                    gridPaint = getRangeGridlinePaint();
4039                    paintLine = true;
4040                }
4041                if (((tick.getValue() != 0.0)
4042                        || !isRangeZeroBaselineVisible()) && paintLine) {
4043                    // the method we want isn't in the CategoryItemRenderer
4044                    // interface...
4045                    if (r instanceof AbstractCategoryItemRenderer) {
4046                        AbstractCategoryItemRenderer aci
4047                                = (AbstractCategoryItemRenderer) r;
4048                        aci.drawRangeLine(g2, this, axis, dataArea,
4049                                tick.getValue(), gridPaint, gridStroke);
4050                    }
4051                    else {
4052                        // we'll have to use the method in the interface, but
4053                        // this doesn't have the paint and stroke settings...
4054                        r.drawRangeGridline(g2, this, axis, dataArea,
4055                                tick.getValue());
4056                    }
4057                }
4058            }
4059        }
4060    
4061        /**
4062         * Draws a base line across the chart at value zero on the range axis.
4063         *
4064         * @param g2  the graphics device.
4065         * @param area  the data area.
4066         *
4067         * @see #setRangeZeroBaselineVisible(boolean)
4068         *
4069         * @since 1.0.13
4070         */
4071        protected void drawZeroRangeBaseline(Graphics2D g2, Rectangle2D area) {
4072            if (!isRangeZeroBaselineVisible()) {
4073                return;
4074            }
4075            CategoryItemRenderer r = getRenderer();
4076            if (r instanceof AbstractCategoryItemRenderer) {
4077                AbstractCategoryItemRenderer aci = (AbstractCategoryItemRenderer) r;
4078                aci.drawRangeLine(g2, this, getRangeAxis(), area, 0.0,
4079                        this.rangeZeroBaselinePaint, this.rangeZeroBaselineStroke);
4080            }
4081            else {
4082                r.drawRangeGridline(g2, this, getRangeAxis(), area, 0.0);
4083            }
4084        }
4085    
4086        /**
4087         * Draws the annotations.
4088         *
4089         * @param g2  the graphics device.
4090         * @param dataArea  the data area.
4091         */
4092        protected void drawAnnotations(Graphics2D g2, Rectangle2D dataArea) {
4093    
4094            if (getAnnotations() != null) {
4095                Iterator iterator = getAnnotations().iterator();
4096                while (iterator.hasNext()) {
4097                    CategoryAnnotation annotation
4098                            = (CategoryAnnotation) iterator.next();
4099                    annotation.draw(g2, this, dataArea, getDomainAxis(),
4100                            getRangeAxis());
4101                }
4102            }
4103    
4104        }
4105    
4106        /**
4107         * Draws the domain markers (if any) for an axis and layer.  This method is
4108         * typically called from within the draw() method.
4109         *
4110         * @param g2  the graphics device.
4111         * @param dataArea  the data area.
4112         * @param index  the renderer index.
4113         * @param layer  the layer (foreground or background).
4114         *
4115         * @see #drawRangeMarkers(Graphics2D, Rectangle2D, int, Layer)
4116         */
4117        protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea,
4118                                         int index, Layer layer) {
4119    
4120            CategoryItemRenderer r = getRenderer(index);
4121            if (r == null) {
4122                return;
4123            }
4124    
4125            Collection markers = getDomainMarkers(index, layer);
4126            CategoryAxis axis = getDomainAxisForDataset(index);
4127            if (markers != null && axis != null) {
4128                Iterator iterator = markers.iterator();
4129                while (iterator.hasNext()) {
4130                    CategoryMarker marker = (CategoryMarker) iterator.next();
4131                    r.drawDomainMarker(g2, this, axis, marker, dataArea);
4132                }
4133            }
4134    
4135        }
4136    
4137        /**
4138         * Draws the range markers (if any) for an axis and layer.  This method is
4139         * typically called from within the draw() method.
4140         *
4141         * @param g2  the graphics device.
4142         * @param dataArea  the data area.
4143         * @param index  the renderer index.
4144         * @param layer  the layer (foreground or background).
4145         *
4146         * @see #drawDomainMarkers(Graphics2D, Rectangle2D, int, Layer)
4147         */
4148        protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea,
4149                                        int index, Layer layer) {
4150    
4151            CategoryItemRenderer r = getRenderer(index);
4152            if (r == null) {
4153                return;
4154            }
4155    
4156            Collection markers = getRangeMarkers(index, layer);
4157            ValueAxis axis = getRangeAxisForDataset(index);
4158            if (markers != null && axis != null) {
4159                Iterator iterator = markers.iterator();
4160                while (iterator.hasNext()) {
4161                    Marker marker = (Marker) iterator.next();
4162                    r.drawRangeMarker(g2, this, axis, marker, dataArea);
4163                }
4164            }
4165    
4166        }
4167    
4168        /**
4169         * Utility method for drawing a line perpendicular to the range axis (used
4170         * for crosshairs).
4171         *
4172         * @param g2  the graphics device.
4173         * @param dataArea  the area defined by the axes.
4174         * @param value  the data value.
4175         * @param stroke  the line stroke (<code>null</code> not permitted).
4176         * @param paint  the line paint (<code>null</code> not permitted).
4177         */
4178        protected void drawRangeLine(Graphics2D g2, Rectangle2D dataArea,
4179                double value, Stroke stroke, Paint paint) {
4180    
4181            double java2D = getRangeAxis().valueToJava2D(value, dataArea,
4182                    getRangeAxisEdge());
4183            Line2D line = null;
4184            if (this.orientation == PlotOrientation.HORIZONTAL) {
4185                line = new Line2D.Double(java2D, dataArea.getMinY(), java2D,
4186                        dataArea.getMaxY());
4187            }
4188            else if (this.orientation == PlotOrientation.VERTICAL) {
4189                line = new Line2D.Double(dataArea.getMinX(), java2D,
4190                        dataArea.getMaxX(), java2D);
4191            }
4192            g2.setStroke(stroke);
4193            g2.setPaint(paint);
4194            g2.draw(line);
4195    
4196        }
4197    
4198        /**
4199         * Draws a domain crosshair.
4200         *
4201         * @param g2  the graphics target.
4202         * @param dataArea  the data area.
4203         * @param orientation  the plot orientation.
4204         * @param datasetIndex  the dataset index.
4205         * @param rowKey  the row key.
4206         * @param columnKey  the column key.
4207         * @param stroke  the stroke used to draw the crosshair line.
4208         * @param paint  the paint used to draw the crosshair line.
4209         *
4210         * @see #drawRangeCrosshair(Graphics2D, Rectangle2D, PlotOrientation,
4211         *     double, ValueAxis, Stroke, Paint)
4212         *
4213         * @since 1.0.11
4214         */
4215        protected void drawDomainCrosshair(Graphics2D g2, Rectangle2D dataArea,
4216                PlotOrientation orientation, int datasetIndex,
4217                Comparable rowKey, Comparable columnKey, Stroke stroke,
4218                Paint paint) {
4219    
4220            CategoryDataset dataset = getDataset(datasetIndex);
4221            CategoryAxis axis = getDomainAxisForDataset(datasetIndex);
4222            CategoryItemRenderer renderer = getRenderer(datasetIndex);
4223            Line2D line = null;
4224            if (orientation == PlotOrientation.VERTICAL) {
4225                double xx = renderer.getItemMiddle(rowKey, columnKey, dataset, axis,
4226                        dataArea, RectangleEdge.BOTTOM);
4227                line = new Line2D.Double(xx, dataArea.getMinY(), xx,
4228                        dataArea.getMaxY());
4229            }
4230            else {
4231                double yy = renderer.getItemMiddle(rowKey, columnKey, dataset, axis,
4232                        dataArea, RectangleEdge.LEFT);
4233                line = new Line2D.Double(dataArea.getMinX(), yy,
4234                        dataArea.getMaxX(), yy);
4235            }
4236            g2.setStroke(stroke);
4237            g2.setPaint(paint);
4238            g2.draw(line);
4239    
4240        }
4241    
4242        /**
4243         * Draws a range crosshair.
4244         *
4245         * @param g2  the graphics target.
4246         * @param dataArea  the data area.
4247         * @param orientation  the plot orientation.
4248         * @param value  the crosshair value.
4249         * @param axis  the axis against which the value is measured.
4250         * @param stroke  the stroke used to draw the crosshair line.
4251         * @param paint  the paint used to draw the crosshair line.
4252         *
4253         * @see #drawDomainCrosshair(Graphics2D, Rectangle2D, PlotOrientation, int,
4254         *      Comparable, Comparable, Stroke, Paint)
4255         *
4256         * @since 1.0.5
4257         */
4258        protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea,
4259                PlotOrientation orientation, double value, ValueAxis axis,
4260                Stroke stroke, Paint paint) {
4261    
4262            if (!axis.getRange().contains(value)) {
4263                return;
4264            }
4265            Line2D line = null;
4266            if (orientation == PlotOrientation.HORIZONTAL) {
4267                double xx = axis.valueToJava2D(value, dataArea,
4268                        RectangleEdge.BOTTOM);
4269                line = new Line2D.Double(xx, dataArea.getMinY(), xx,
4270                        dataArea.getMaxY());
4271            }
4272            else {
4273                double yy = axis.valueToJava2D(value, dataArea,
4274                        RectangleEdge.LEFT);
4275                line = new Line2D.Double(dataArea.getMinX(), yy,
4276                        dataArea.getMaxX(), yy);
4277            }
4278            g2.setStroke(stroke);
4279            g2.setPaint(paint);
4280            g2.draw(line);
4281    
4282        }
4283    
4284        /**
4285         * Returns the range of data values that will be plotted against the range
4286         * axis.  If the dataset is <code>null</code>, this method returns
4287         * <code>null</code>.
4288         *
4289         * @param axis  the axis.
4290         *
4291         * @return The data range.
4292         */
4293        public Range getDataRange(ValueAxis axis) {
4294    
4295            Range result = null;
4296            List mappedDatasets = new ArrayList();
4297    
4298            int rangeIndex = this.rangeAxes.indexOf(axis);
4299            if (rangeIndex >= 0) {
4300                mappedDatasets.addAll(datasetsMappedToRangeAxis(rangeIndex));
4301            }
4302            else if (axis == getRangeAxis()) {
4303                mappedDatasets.addAll(datasetsMappedToRangeAxis(0));
4304            }
4305    
4306            // iterate through the datasets that map to the axis and get the union
4307            // of the ranges.
4308            Iterator iterator = mappedDatasets.iterator();
4309            while (iterator.hasNext()) {
4310                CategoryDataset d = (CategoryDataset) iterator.next();
4311                CategoryItemRenderer r = getRendererForDataset(d);
4312                if (r != null) {
4313                    result = Range.combine(result, r.findRangeBounds(d));
4314                }
4315            }
4316            return result;
4317    
4318        }
4319    
4320        /**
4321         * Returns a list of the datasets that are mapped to the axis with the
4322         * specified index.
4323         *
4324         * @param axisIndex  the axis index.
4325         *
4326         * @return The list (possibly empty, but never <code>null</code>).
4327         *
4328         * @since 1.0.3
4329         */
4330        private List datasetsMappedToDomainAxis(int axisIndex) {
4331            Integer key = new Integer(axisIndex);
4332            List result = new ArrayList();
4333            for (int i = 0; i < this.datasets.size(); i++) {
4334                List mappedAxes = (List) this.datasetToDomainAxesMap.get(
4335                        new Integer(i));
4336                CategoryDataset dataset = (CategoryDataset) this.datasets.get(i);
4337                if (mappedAxes == null) {
4338                    if (key.equals(ZERO)) {
4339                        if (dataset != null) {
4340                            result.add(dataset);
4341                        }
4342                    }
4343                }
4344                else {
4345                    if (mappedAxes.contains(key)) {
4346                        if (dataset != null) {
4347                            result.add(dataset);
4348                        }
4349                    }
4350                }
4351            }
4352            return result;
4353        }
4354    
4355        /**
4356         * A utility method that returns a list of datasets that are mapped to a
4357         * given range axis.
4358         *
4359         * @param index  the axis index.
4360         *
4361         * @return A list of datasets.
4362         */
4363        private List datasetsMappedToRangeAxis(int index) {
4364            Integer key = new Integer(index);
4365            List result = new ArrayList();
4366            for (int i = 0; i < this.datasets.size(); i++) {
4367                List mappedAxes = (List) this.datasetToRangeAxesMap.get(
4368                        new Integer(i));
4369                if (mappedAxes == null) {
4370                    if (key.equals(ZERO)) {
4371                        result.add(this.datasets.get(i));
4372                    }
4373                }
4374                else {
4375                    if (mappedAxes.contains(key)) {
4376                        result.add(this.datasets.get(i));
4377                    }
4378                }
4379            }
4380            return result;
4381        }
4382    
4383        /**
4384         * Returns the weight for this plot when it is used as a subplot within a
4385         * combined plot.
4386         *
4387         * @return The weight.
4388         *
4389         * @see #setWeight(int)
4390         */
4391        public int getWeight() {
4392            return this.weight;
4393        }
4394    
4395        /**
4396         * Sets the weight for the plot and sends a {@link PlotChangeEvent} to all
4397         * registered listeners.
4398         *
4399         * @param weight  the weight.
4400         *
4401         * @see #getWeight()
4402         */
4403        public void setWeight(int weight) {
4404            this.weight = weight;
4405            fireChangeEvent();
4406        }
4407    
4408        /**
4409         * Returns the fixed domain axis space.
4410         *
4411         * @return The fixed domain axis space (possibly <code>null</code>).
4412         *
4413         * @see #setFixedDomainAxisSpace(AxisSpace)
4414         */
4415        public AxisSpace getFixedDomainAxisSpace() {
4416            return this.fixedDomainAxisSpace;
4417        }
4418    
4419        /**
4420         * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
4421         * all registered listeners.
4422         *
4423         * @param space  the space (<code>null</code> permitted).
4424         *
4425         * @see #getFixedDomainAxisSpace()
4426         */
4427        public void setFixedDomainAxisSpace(AxisSpace space) {
4428            setFixedDomainAxisSpace(space, true);
4429        }
4430    
4431        /**
4432         * Sets the fixed domain axis space and sends a {@link PlotChangeEvent} to
4433         * all registered listeners.
4434         *
4435         * @param space  the space (<code>null</code> permitted).
4436         * @param notify  notify listeners?
4437         *
4438         * @see #getFixedDomainAxisSpace()
4439         *
4440         * @since 1.0.7
4441         */
4442        public void setFixedDomainAxisSpace(AxisSpace space, boolean notify) {
4443            this.fixedDomainAxisSpace = space;
4444            if (notify) {
4445                fireChangeEvent();
4446            }
4447        }
4448    
4449        /**
4450         * Returns the fixed range axis space.
4451         *
4452         * @return The fixed range axis space (possibly <code>null</code>).
4453         *
4454         * @see #setFixedRangeAxisSpace(AxisSpace)
4455         */
4456        public AxisSpace getFixedRangeAxisSpace() {
4457            return this.fixedRangeAxisSpace;
4458        }
4459    
4460        /**
4461         * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
4462         * all registered listeners.
4463         *
4464         * @param space  the space (<code>null</code> permitted).
4465         *
4466         * @see #getFixedRangeAxisSpace()
4467         */
4468        public void setFixedRangeAxisSpace(AxisSpace space) {
4469            setFixedRangeAxisSpace(space, true);
4470        }
4471    
4472        /**
4473         * Sets the fixed range axis space and sends a {@link PlotChangeEvent} to
4474         * all registered listeners.
4475         *
4476         * @param space  the space (<code>null</code> permitted).
4477         * @param notify  notify listeners?
4478         *
4479         * @see #getFixedRangeAxisSpace()
4480         *
4481         * @since 1.0.7
4482         */
4483        public void setFixedRangeAxisSpace(AxisSpace space, boolean notify) {
4484            this.fixedRangeAxisSpace = space;
4485            if (notify) {
4486                fireChangeEvent();
4487            }
4488        }
4489    
4490        /**
4491         * Returns a list of the categories in the plot's primary dataset.
4492         *
4493         * @return A list of the categories in the plot's primary dataset.
4494         *
4495         * @see #getCategoriesForAxis(CategoryAxis)
4496         */
4497        public List getCategories() {
4498            List result = null;
4499            if (getDataset() != null) {
4500                result = Collections.unmodifiableList(getDataset().getColumnKeys());
4501            }
4502            return result;
4503        }
4504    
4505        /**
4506         * Returns a list of the categories that should be displayed for the
4507         * specified axis.
4508         *
4509         * @param axis  the axis (<code>null</code> not permitted)
4510         *
4511         * @return The categories.
4512         *
4513         * @since 1.0.3
4514         */
4515        public List getCategoriesForAxis(CategoryAxis axis) {
4516            List result = new ArrayList();
4517            int axisIndex = this.domainAxes.indexOf(axis);
4518            List datasets = datasetsMappedToDomainAxis(axisIndex);
4519            Iterator iterator = datasets.iterator();
4520            while (iterator.hasNext()) {
4521                CategoryDataset dataset = (CategoryDataset) iterator.next();
4522                // add the unique categories from this dataset
4523                for (int i = 0; i < dataset.getColumnCount(); i++) {
4524                    Comparable category = dataset.getColumnKey(i);
4525                    if (!result.contains(category)) {
4526                        result.add(category);
4527                    }
4528                }
4529            }
4530            return result;
4531        }
4532    
4533        /**
4534         * Returns the flag that controls whether or not the shared domain axis is
4535         * drawn for each subplot.
4536         *
4537         * @return A boolean.
4538         *
4539         * @see #setDrawSharedDomainAxis(boolean)
4540         */
4541        public boolean getDrawSharedDomainAxis() {
4542            return this.drawSharedDomainAxis;
4543        }
4544    
4545        /**
4546         * Sets the flag that controls whether the shared domain axis is drawn when
4547         * this plot is being used as a subplot.
4548         *
4549         * @param draw  a boolean.
4550         *
4551         * @see #getDrawSharedDomainAxis()
4552         */
4553        public void setDrawSharedDomainAxis(boolean draw) {
4554            this.drawSharedDomainAxis = draw;
4555            fireChangeEvent();
4556        }
4557    
4558        /**
4559         * Returns <code>false</code> always, because the plot cannot be panned
4560         * along the domain axis/axes.
4561         *
4562         * @return A boolean.
4563         *
4564         * @see #isRangePannable()
4565         *
4566         * @since 1.0.13
4567         */
4568        public boolean isDomainPannable() {
4569            return false;
4570        }
4571    
4572        /**
4573         * Returns <code>true</code> if panning is enabled for the range axes,
4574         * and <code>false</code> otherwise.
4575         *
4576         * @return A boolean.
4577         *
4578         * @see #setRangePannable(boolean)
4579         * @see #isDomainPannable()
4580         *
4581         * @since 1.0.13
4582         */
4583        public boolean isRangePannable() {
4584            return this.rangePannable;
4585        }
4586    
4587        /**
4588         * Sets the flag that enables or disables panning of the plot along
4589         * the range axes.
4590         *
4591         * @param pannable  the new flag value.
4592         *
4593         * @see #isRangePannable() 
4594         *
4595         * @since 1.0.13
4596         */
4597        public void setRangePannable(boolean pannable) {
4598            this.rangePannable = pannable;
4599        }
4600    
4601        /**
4602         * Pans the domain axes by the specified percentage.
4603         *
4604         * @param percent  the distance to pan (as a percentage of the axis length).
4605         * @param info the plot info
4606         * @param source the source point where the pan action started.
4607         *
4608         * @since 1.0.13
4609         */
4610        public void panDomainAxes(double percent, PlotRenderingInfo info,
4611                Point2D source) {
4612            // do nothing, because the plot is not pannable along the domain axes
4613        }
4614    
4615        /**
4616         * Pans the range axes by the specified percentage.
4617         *
4618         * @param percent  the distance to pan (as a percentage of the axis length).
4619         * @param info the plot info
4620         * @param source the source point where the pan action started.
4621         *
4622         * @since 1.0.13
4623         */
4624        public void panRangeAxes(double percent, PlotRenderingInfo info,
4625                Point2D source) {
4626            if (!isRangePannable()) {
4627                return;
4628            }
4629            int rangeAxisCount = getRangeAxisCount();
4630            for (int i = 0; i < rangeAxisCount; i++) {
4631                ValueAxis axis = getRangeAxis(i);
4632                if (axis == null) {
4633                    continue;
4634                }
4635                double length = axis.getRange().getLength();
4636                double adj = percent * length;
4637                if (axis.isInverted()) {
4638                    adj = -adj;
4639                }
4640                axis.setRange(axis.getLowerBound() + adj,
4641                        axis.getUpperBound() + adj);
4642            }
4643        }
4644    
4645        /**
4646         * Returns <code>false</code> to indicate that the domain axes are not
4647         * zoomable.
4648         *
4649         * @return A boolean.
4650         *
4651         * @see #isRangeZoomable()
4652         */
4653        public boolean isDomainZoomable() {
4654            return false;
4655        }
4656    
4657        /**
4658         * Returns <code>true</code> to indicate that the range axes are zoomable.
4659         *
4660         * @return A boolean.
4661         *
4662         * @see #isDomainZoomable()
4663         */
4664        public boolean isRangeZoomable() {
4665            return true;
4666        }
4667    
4668        /**
4669         * This method does nothing, because <code>CategoryPlot</code> doesn't
4670         * support zooming on the domain.
4671         *
4672         * @param factor  the zoom factor.
4673         * @param state  the plot state.
4674         * @param source  the source point (in Java2D space) for the zoom.
4675         */
4676        public void zoomDomainAxes(double factor, PlotRenderingInfo state,
4677                                   Point2D source) {
4678            // can't zoom domain axis
4679        }
4680    
4681        /**
4682         * This method does nothing, because <code>CategoryPlot</code> doesn't
4683         * support zooming on the domain.
4684         *
4685         * @param lowerPercent  the lower bound.
4686         * @param upperPercent  the upper bound.
4687         * @param state  the plot state.
4688         * @param source  the source point (in Java2D space) for the zoom.
4689         */
4690        public void zoomDomainAxes(double lowerPercent, double upperPercent,
4691                                   PlotRenderingInfo state, Point2D source) {
4692            // can't zoom domain axis
4693        }
4694    
4695        /**
4696         * This method does nothing, because <code>CategoryPlot</code> doesn't
4697         * support zooming on the domain.
4698         *
4699         * @param factor  the zoom factor.
4700         * @param info  the plot rendering info.
4701         * @param source  the source point (in Java2D space).
4702         * @param useAnchor  use source point as zoom anchor?
4703         *
4704         * @see #zoomRangeAxes(double, PlotRenderingInfo, Point2D, boolean)
4705         *
4706         * @since 1.0.7
4707         */
4708        public void zoomDomainAxes(double factor, PlotRenderingInfo info,
4709                                   Point2D source, boolean useAnchor) {
4710            // can't zoom domain axis
4711        }
4712    
4713        /**
4714         * Multiplies the range on the range axis/axes by the specified factor.
4715         *
4716         * @param factor  the zoom factor.
4717         * @param state  the plot state.
4718         * @param source  the source point (in Java2D space) for the zoom.
4719         */
4720        public void zoomRangeAxes(double factor, PlotRenderingInfo state,
4721                                  Point2D source) {
4722            // delegate to other method
4723            zoomRangeAxes(factor, state, source, false);
4724        }
4725    
4726        /**
4727         * Multiplies the range on the range axis/axes by the specified factor.
4728         *
4729         * @param factor  the zoom factor.
4730         * @param info  the plot rendering info.
4731         * @param source  the source point.
4732         * @param useAnchor  a flag that controls whether or not the source point
4733         *         is used for the zoom anchor.
4734         *
4735         * @see #zoomDomainAxes(double, PlotRenderingInfo, Point2D, boolean)
4736         *
4737         * @since 1.0.7
4738         */
4739        public void zoomRangeAxes(double factor, PlotRenderingInfo info,
4740                                  Point2D source, boolean useAnchor) {
4741    
4742            // perform the zoom on each range axis
4743            for (int i = 0; i < this.rangeAxes.size(); i++) {
4744                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
4745                if (rangeAxis != null) {
4746                    if (useAnchor) {
4747                        // get the relevant source coordinate given the plot
4748                        // orientation
4749                        double sourceY = source.getY();
4750                        if (this.orientation == PlotOrientation.HORIZONTAL) {
4751                            sourceY = source.getX();
4752                        }
4753                        double anchorY = rangeAxis.java2DToValue(sourceY,
4754                                info.getDataArea(), getRangeAxisEdge());
4755                        rangeAxis.resizeRange2(factor, anchorY);
4756                    }
4757                    else {
4758                        rangeAxis.resizeRange(factor);
4759                    }
4760                }
4761            }
4762        }
4763    
4764        /**
4765         * Zooms in on the range axes.
4766         *
4767         * @param lowerPercent  the lower bound.
4768         * @param upperPercent  the upper bound.
4769         * @param state  the plot state.
4770         * @param source  the source point (in Java2D space) for the zoom.
4771         */
4772        public void zoomRangeAxes(double lowerPercent, double upperPercent,
4773                                  PlotRenderingInfo state, Point2D source) {
4774            for (int i = 0; i < this.rangeAxes.size(); i++) {
4775                ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
4776                if (rangeAxis != null) {
4777                    rangeAxis.zoomRange(lowerPercent, upperPercent);
4778                }
4779            }
4780        }
4781    
4782        /**
4783         * Returns the anchor value.
4784         *
4785         * @return The anchor value.
4786         *
4787         * @see #setAnchorValue(double)
4788         */
4789        public double getAnchorValue() {
4790            return this.anchorValue;
4791        }
4792    
4793        /**
4794         * Sets the anchor value and sends a {@link PlotChangeEvent} to all
4795         * registered listeners.
4796         *
4797         * @param value  the anchor value.
4798         *
4799         * @see #getAnchorValue()
4800         */
4801        public void setAnchorValue(double value) {
4802            setAnchorValue(value, true);
4803        }
4804    
4805        /**
4806         * Sets the anchor value and, if requested, sends a {@link PlotChangeEvent}
4807         * to all registered listeners.
4808         *
4809         * @param value  the value.
4810         * @param notify  notify listeners?
4811         *
4812         * @see #getAnchorValue()
4813         */
4814        public void setAnchorValue(double value, boolean notify) {
4815            this.anchorValue = value;
4816            if (notify) {
4817                fireChangeEvent();
4818            }
4819        }
4820    
4821        /**
4822         * Tests the plot for equality with an arbitrary object.
4823         *
4824         * @param obj  the object to test against (<code>null</code> permitted).
4825         *
4826         * @return A boolean.
4827         */
4828        public boolean equals(Object obj) {
4829            if (obj == this) {
4830                return true;
4831            }
4832            if (!(obj instanceof CategoryPlot)) {
4833                return false;
4834            }
4835            CategoryPlot that = (CategoryPlot) obj;
4836            if (this.orientation != that.orientation) {
4837                return false;
4838            }
4839            if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) {
4840                return false;
4841            }
4842            if (!this.domainAxes.equals(that.domainAxes)) {
4843                return false;
4844            }
4845            if (!this.domainAxisLocations.equals(that.domainAxisLocations)) {
4846                return false;
4847            }
4848            if (this.drawSharedDomainAxis != that.drawSharedDomainAxis) {
4849                return false;
4850            }
4851            if (!this.rangeAxes.equals(that.rangeAxes)) {
4852                return false;
4853            }
4854            if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) {
4855                return false;
4856            }
4857            if (!ObjectUtilities.equal(this.datasetToDomainAxesMap,
4858                    that.datasetToDomainAxesMap)) {
4859                return false;
4860            }
4861            if (!ObjectUtilities.equal(this.datasetToRangeAxesMap,
4862                    that.datasetToRangeAxesMap)) {
4863                return false;
4864            }
4865            if (!ObjectUtilities.equal(this.renderers, that.renderers)) {
4866                return false;
4867            }
4868            if (this.renderingOrder != that.renderingOrder) {
4869                return false;
4870            }
4871            if (this.columnRenderingOrder != that.columnRenderingOrder) {
4872                return false;
4873            }
4874            if (this.rowRenderingOrder != that.rowRenderingOrder) {
4875                return false;
4876            }
4877            if (this.domainGridlinesVisible != that.domainGridlinesVisible) {
4878                return false;
4879            }
4880            if (this.domainGridlinePosition != that.domainGridlinePosition) {
4881                return false;
4882            }
4883            if (!ObjectUtilities.equal(this.domainGridlineStroke,
4884                    that.domainGridlineStroke)) {
4885                return false;
4886            }
4887            if (!PaintUtilities.equal(this.domainGridlinePaint,
4888                    that.domainGridlinePaint)) {
4889                return false;
4890            }
4891            if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) {
4892                return false;
4893            }
4894            if (!ObjectUtilities.equal(this.rangeGridlineStroke,
4895                    that.rangeGridlineStroke)) {
4896                return false;
4897            }
4898            if (!PaintUtilities.equal(this.rangeGridlinePaint,
4899                    that.rangeGridlinePaint)) {
4900                return false;
4901            }
4902            if (this.anchorValue != that.anchorValue) {
4903                return false;
4904            }
4905            if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) {
4906                return false;
4907            }
4908            if (this.rangeCrosshairValue != that.rangeCrosshairValue) {
4909                return false;
4910            }
4911            if (!ObjectUtilities.equal(this.rangeCrosshairStroke,
4912                    that.rangeCrosshairStroke)) {
4913                return false;
4914            }
4915            if (!PaintUtilities.equal(this.rangeCrosshairPaint,
4916                    that.rangeCrosshairPaint)) {
4917                return false;
4918            }
4919            if (this.rangeCrosshairLockedOnData
4920                    != that.rangeCrosshairLockedOnData) {
4921                return false;
4922            }
4923            if (!ObjectUtilities.equal(this.foregroundDomainMarkers,
4924                    that.foregroundDomainMarkers)) {
4925                return false;
4926            }
4927            if (!ObjectUtilities.equal(this.backgroundDomainMarkers,
4928                    that.backgroundDomainMarkers)) {
4929                return false;
4930            }
4931            if (!ObjectUtilities.equal(this.foregroundRangeMarkers,
4932                    that.foregroundRangeMarkers)) {
4933                return false;
4934            }
4935            if (!ObjectUtilities.equal(this.backgroundRangeMarkers,
4936                    that.backgroundRangeMarkers)) {
4937                return false;
4938            }
4939            if (!ObjectUtilities.equal(this.annotations, that.annotations)) {
4940                return false;
4941            }
4942            if (this.weight != that.weight) {
4943                return false;
4944            }
4945            if (!ObjectUtilities.equal(this.fixedDomainAxisSpace,
4946                    that.fixedDomainAxisSpace)) {
4947                return false;
4948            }
4949            if (!ObjectUtilities.equal(this.fixedRangeAxisSpace,
4950                    that.fixedRangeAxisSpace)) {
4951                return false;
4952            }
4953            if (!ObjectUtilities.equal(this.fixedLegendItems,
4954                    that.fixedLegendItems)) {
4955                return false;
4956            }
4957            if (this.domainCrosshairVisible != that.domainCrosshairVisible) {
4958                return false;
4959            }
4960            if (this.crosshairDatasetIndex != that.crosshairDatasetIndex) {
4961                return false;
4962            }
4963            if (!ObjectUtilities.equal(this.domainCrosshairColumnKey,
4964                    that.domainCrosshairColumnKey)) {
4965                return false;
4966            }
4967            if (!ObjectUtilities.equal(this.domainCrosshairRowKey,
4968                    that.domainCrosshairRowKey)) {
4969                return false;
4970            }
4971            if (!PaintUtilities.equal(this.domainCrosshairPaint,
4972                    that.domainCrosshairPaint)) {
4973                return false;
4974            }
4975            if (!ObjectUtilities.equal(this.domainCrosshairStroke,
4976                    that.domainCrosshairStroke)) {
4977                return false;
4978            }
4979            if (this.rangeMinorGridlinesVisible
4980                    != that.rangeMinorGridlinesVisible) {
4981                return false;
4982            }
4983            if (!PaintUtilities.equal(this.rangeMinorGridlinePaint,
4984                    that.rangeMinorGridlinePaint)) {
4985                return false;
4986            }
4987            if (!ObjectUtilities.equal(this.rangeMinorGridlineStroke,
4988                    that.rangeMinorGridlineStroke)) {
4989                return false;
4990            }
4991            if (this.rangeZeroBaselineVisible != that.rangeZeroBaselineVisible) {
4992                return false;
4993            }
4994            if (!PaintUtilities.equal(this.rangeZeroBaselinePaint,
4995                    that.rangeZeroBaselinePaint)) {
4996                return false;
4997            }
4998            if (!ObjectUtilities.equal(this.rangeZeroBaselineStroke,
4999                    that.rangeZeroBaselineStroke)) {
5000                return false;
5001            }
5002            if (!ObjectUtilities.equal(this.shadowGenerator,
5003                    that.shadowGenerator)) {
5004                return false;
5005            }
5006            return super.equals(obj);
5007        }
5008    
5009        /**
5010         * Returns a clone of the plot.
5011         *
5012         * @return A clone.
5013         *
5014         * @throws CloneNotSupportedException  if the cloning is not supported.
5015         */
5016        public Object clone() throws CloneNotSupportedException {
5017    
5018            CategoryPlot clone = (CategoryPlot) super.clone();
5019    
5020            clone.domainAxes = new ObjectList();
5021            for (int i = 0; i < this.domainAxes.size(); i++) {
5022                CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i);
5023                if (xAxis != null) {
5024                    CategoryAxis clonedAxis = (CategoryAxis) xAxis.clone();
5025                    clone.setDomainAxis(i, clonedAxis);
5026                }
5027            }
5028            clone.domainAxisLocations
5029                    = (ObjectList) this.domainAxisLocations.clone();
5030    
5031            clone.rangeAxes = new ObjectList();
5032            for (int i = 0; i < this.rangeAxes.size(); i++) {
5033                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i);
5034                if (yAxis != null) {
5035                    ValueAxis clonedAxis = (ValueAxis) yAxis.clone();
5036                    clone.setRangeAxis(i, clonedAxis);
5037                }
5038            }
5039            clone.rangeAxisLocations = (ObjectList) this.rangeAxisLocations.clone();
5040    
5041            clone.datasets = (ObjectList) this.datasets.clone();
5042            for (int i = 0; i < clone.datasets.size(); i++) {
5043                CategoryDataset dataset = clone.getDataset(i);
5044                if (dataset != null) {
5045                    dataset.addChangeListener(clone);
5046                }
5047            }
5048            clone.datasetToDomainAxesMap = new TreeMap();
5049            clone.datasetToDomainAxesMap.putAll(this.datasetToDomainAxesMap);
5050            clone.datasetToRangeAxesMap = new TreeMap();
5051            clone.datasetToRangeAxesMap.putAll(this.datasetToRangeAxesMap);
5052    
5053            clone.renderers = (ObjectList) this.renderers.clone();
5054            for (int i = 0; i < this.renderers.size(); i++) {
5055                CategoryItemRenderer renderer2 = (CategoryItemRenderer)
5056                        this.renderers.get(i);
5057                if (renderer2 instanceof PublicCloneable) {
5058                    PublicCloneable pc = (PublicCloneable) renderer2;
5059                    CategoryItemRenderer rc = (CategoryItemRenderer) pc.clone();
5060                    clone.renderers.set(i, rc);
5061                    rc.setPlot(clone);
5062                    rc.addChangeListener(clone);
5063                }
5064            }
5065            if (this.fixedDomainAxisSpace != null) {
5066                clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtilities.clone(
5067                        this.fixedDomainAxisSpace);
5068            }
5069            if (this.fixedRangeAxisSpace != null) {
5070                clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtilities.clone(
5071                        this.fixedRangeAxisSpace);
5072            }
5073    
5074            clone.annotations = (List) ObjectUtilities.deepClone(this.annotations);
5075            clone.foregroundDomainMarkers = cloneMarkerMap(
5076                    this.foregroundDomainMarkers);
5077            clone.backgroundDomainMarkers = cloneMarkerMap(
5078                    this.backgroundDomainMarkers);
5079            clone.foregroundRangeMarkers = cloneMarkerMap(
5080                    this.foregroundRangeMarkers);
5081            clone.backgroundRangeMarkers = cloneMarkerMap(
5082                    this.backgroundRangeMarkers);
5083            if (this.fixedLegendItems != null) {
5084                clone.fixedLegendItems
5085                        = (LegendItemCollection) this.fixedLegendItems.clone();
5086            }
5087            return clone;
5088    
5089        }
5090    
5091        /**
5092         * A utility method to clone the marker maps.
5093         *
5094         * @param map  the map to clone.
5095         *
5096         * @return A clone of the map.
5097         *
5098         * @throws CloneNotSupportedException if there is some problem cloning the
5099         *                                    map.
5100         */
5101        private Map cloneMarkerMap(Map map) throws CloneNotSupportedException {
5102            Map clone = new HashMap();
5103            Set keys = map.keySet();
5104            Iterator iterator = keys.iterator();
5105            while (iterator.hasNext()) {
5106                Object key = iterator.next();
5107                List entry = (List) map.get(key);
5108                Object toAdd = ObjectUtilities.deepClone(entry);
5109                clone.put(key, toAdd);
5110            }
5111            return clone;
5112        }
5113    
5114        /**
5115         * Provides serialization support.
5116         *
5117         * @param stream  the output stream.
5118         *
5119         * @throws IOException  if there is an I/O error.
5120         */
5121        private void writeObject(ObjectOutputStream stream) throws IOException {
5122            stream.defaultWriteObject();
5123            SerialUtilities.writeStroke(this.domainGridlineStroke, stream);
5124            SerialUtilities.writePaint(this.domainGridlinePaint, stream);
5125            SerialUtilities.writeStroke(this.rangeGridlineStroke, stream);
5126            SerialUtilities.writePaint(this.rangeGridlinePaint, stream);
5127            SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream);
5128            SerialUtilities.writePaint(this.rangeCrosshairPaint, stream);
5129            SerialUtilities.writeStroke(this.domainCrosshairStroke, stream);
5130            SerialUtilities.writePaint(this.domainCrosshairPaint, stream);
5131            SerialUtilities.writeStroke(this.rangeMinorGridlineStroke, stream);
5132            SerialUtilities.writePaint(this.rangeMinorGridlinePaint, stream);
5133            SerialUtilities.writeStroke(this.rangeZeroBaselineStroke, stream);
5134            SerialUtilities.writePaint(this.rangeZeroBaselinePaint, stream);
5135        }
5136    
5137        /**
5138         * Provides serialization support.
5139         *
5140         * @param stream  the input stream.
5141         *
5142         * @throws IOException  if there is an I/O error.
5143         * @throws ClassNotFoundException  if there is a classpath problem.
5144         */
5145        private void readObject(ObjectInputStream stream)
5146            throws IOException, ClassNotFoundException {
5147    
5148            stream.defaultReadObject();
5149            this.domainGridlineStroke = SerialUtilities.readStroke(stream);
5150            this.domainGridlinePaint = SerialUtilities.readPaint(stream);
5151            this.rangeGridlineStroke = SerialUtilities.readStroke(stream);
5152            this.rangeGridlinePaint = SerialUtilities.readPaint(stream);
5153            this.rangeCrosshairStroke = SerialUtilities.readStroke(stream);
5154            this.rangeCrosshairPaint = SerialUtilities.readPaint(stream);
5155            this.domainCrosshairStroke = SerialUtilities.readStroke(stream);
5156            this.domainCrosshairPaint = SerialUtilities.readPaint(stream);
5157            this.rangeMinorGridlineStroke = SerialUtilities.readStroke(stream);
5158            this.rangeMinorGridlinePaint = SerialUtilities.readPaint(stream);
5159            this.rangeZeroBaselineStroke = SerialUtilities.readStroke(stream);
5160            this.rangeZeroBaselinePaint = SerialUtilities.readPaint(stream);
5161    
5162            for (int i = 0; i < this.domainAxes.size(); i++) {
5163                CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i);
5164                if (xAxis != null) {
5165                    xAxis.setPlot(this);
5166                    xAxis.addChangeListener(this);
5167                }
5168            }
5169            for (int i = 0; i < this.rangeAxes.size(); i++) {
5170                ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i);
5171                if (yAxis != null) {
5172                    yAxis.setPlot(this);
5173                    yAxis.addChangeListener(this);
5174                }
5175            }
5176            int datasetCount = this.datasets.size();
5177            for (int i = 0; i < datasetCount; i++) {
5178                Dataset dataset = (Dataset) this.datasets.get(i);
5179                if (dataset != null) {
5180                    dataset.addChangeListener(this);
5181                }
5182            }
5183            int rendererCount = this.renderers.size();
5184            for (int i = 0; i < rendererCount; i++) {
5185                CategoryItemRenderer renderer
5186                    = (CategoryItemRenderer) this.renderers.get(i);
5187                if (renderer != null) {
5188                    renderer.addChangeListener(this);
5189                }
5190            }
5191    
5192        }
5193    
5194    }