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