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