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