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