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