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