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