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