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