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