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