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