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