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     * 25-Nov-2008 : Added facility to map datasets to multiples axes (DG);
165     * 15-Dec-2008 : Cleaned up grid drawing methods (DG);
166     * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
167     *               Jess Thrysoee (DG);
168     *
169     */
170    
171    package org.jfree.chart.plot;
172    
173    import java.awt.AlphaComposite;
174    import java.awt.BasicStroke;
175    import java.awt.Color;
176    import java.awt.Composite;
177    import java.awt.Font;
178    import java.awt.Graphics2D;
179    import java.awt.Paint;
180    import java.awt.Shape;
181    import java.awt.Stroke;
182    import java.awt.geom.Line2D;
183    import java.awt.geom.Point2D;
184    import java.awt.geom.Rectangle2D;
185    import java.io.IOException;
186    import java.io.ObjectInputStream;
187    import java.io.ObjectOutputStream;
188    import java.io.Serializable;
189    import java.util.ArrayList;
190    import java.util.Collection;
191    import java.util.Collections;
192    import java.util.HashMap;
193    import java.util.HashSet;
194    import java.util.Iterator;
195    import java.util.List;
196    import java.util.Map;
197    import java.util.ResourceBundle;
198    import java.util.Set;
199    import java.util.TreeMap;
200    
201    import org.jfree.chart.LegendItem;
202    import org.jfree.chart.LegendItemCollection;
203    import org.jfree.chart.annotations.CategoryAnnotation;
204    import org.jfree.chart.axis.Axis;
205    import org.jfree.chart.axis.AxisCollection;
206    import org.jfree.chart.axis.AxisLocation;
207    import org.jfree.chart.axis.AxisSpace;
208    import org.jfree.chart.axis.AxisState;
209    import org.jfree.chart.axis.CategoryAnchor;
210    import org.jfree.chart.axis.CategoryAxis;
211    import org.jfree.chart.axis.ValueAxis;
212    import org.jfree.chart.axis.ValueTick;
213    import org.jfree.chart.event.ChartChangeEventType;
214    import org.jfree.chart.event.PlotChangeEvent;
215    import org.jfree.chart.event.RendererChangeEvent;
216    import org.jfree.chart.event.RendererChangeListener;
217    import org.jfree.chart.renderer.category.CategoryItemRenderer;
218    import org.jfree.chart.renderer.category.CategoryItemRendererState;
219    import org.jfree.chart.util.ResourceBundleWrapper;
220    import org.jfree.data.Range;
221    import org.jfree.data.category.CategoryDataset;
222    import org.jfree.data.general.Dataset;
223    import org.jfree.data.general.DatasetChangeEvent;
224    import org.jfree.data.general.DatasetUtilities;
225    import org.jfree.io.SerialUtilities;
226    import org.jfree.ui.Layer;
227    import org.jfree.ui.RectangleEdge;
228    import org.jfree.ui.RectangleInsets;
229    import org.jfree.util.ObjectList;
230    import org.jfree.util.ObjectUtilities;
231    import org.jfree.util.PaintUtilities;
232    import org.jfree.util.PublicCloneable;
233    import org.jfree.util.ShapeUtilities;
234    import org.jfree.util.SortOrder;
235    
236    /**
237     * A general plotting class that uses data from a {@link CategoryDataset} and
238     * renders each data item using a {@link CategoryItemRenderer}.
239     */
240    public class CategoryPlot extends Plot implements ValueAxisPlot,
241            Zoomable, RendererChangeListener, Cloneable, PublicCloneable,
242            Serializable {
243    
244        /** For serialization. */
245        private static final long serialVersionUID = -3537691700434728188L;
246    
247        /**
248         * The default visibility of the grid lines plotted against the domain
249         * axis.
250         */
251        public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false;
252    
253        /**
254         * The default visibility of the grid lines plotted against the range
255         * axis.
256         */
257        public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true;
258    
259        /** The default grid line stroke. */
260        public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f,
261                BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[]
262                {2.0f, 2.0f}, 0.0f);
263    
264        /** The default grid line paint. */
265        public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray;
266    
267        /** The default value label font. */
268        public static final Font DEFAULT_VALUE_LABEL_FONT = new Font("SansSerif",
269                Font.PLAIN, 10);
270    
271        /**
272         * The default crosshair visibility.
273         *
274         * @since 1.0.5
275         */
276        public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;
277    
278        /**
279         * The default crosshair stroke.
280         *
281         * @since 1.0.5
282         */
283        public static final Stroke DEFAULT_CROSSHAIR_STROKE
284                = DEFAULT_GRIDLINE_STROKE;
285    
286        /**
287         * The default crosshair paint.
288         *
289         * @since 1.0.5
290         */
291        public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue;
292    
293        /** The resourceBundle for the localization. */
294        protected static ResourceBundle localizationResources
295                = ResourceBundleWrapper.getBundle(
296                "org.jfree.chart.plot.LocalizationBundle");
297    
298        /** The plot orientation. */
299        private PlotOrientation orientation;
300    
301        /** The offset between the data area and the axes. */
302        private RectangleInsets axisOffset;
303    
304        /** Storage for the domain axes. */
305        private ObjectList domainAxes;
306    
307        /** Storage for the domain axis locations. */
308        private ObjectList domainAxisLocations;
309    
310        /**
311         * A flag that controls whether or not the shared domain axis is drawn
312         * (only relevant when the plot is being used as a subplot).
313         */
314        private boolean drawSharedDomainAxis;
315    
316        /** Storage for the range axes. */
317        private ObjectList rangeAxes;
318    
319        /** Storage for the range axis locations. */
320        private ObjectList rangeAxisLocations;
321    
322        /** Storage for the datasets. */
323        private ObjectList datasets;
324    
325        /** Storage for keys that map datasets to domain axes. */
326        private TreeMap datasetToDomainAxesMap;
327    
328        /** Storage for keys that map datasets to range axes. */
329        private TreeMap datasetToRangeAxesMap;
330    
331        /** Storage for the renderers. */
332        private ObjectList renderers;
333    
334        /** The dataset rendering order. */
335        private DatasetRenderingOrder renderingOrder
336                = DatasetRenderingOrder.REVERSE;
337    
338        /**
339         * Controls the order in which the columns are traversed when rendering the
340         * data items.
341         */
342        private SortOrder columnRenderingOrder = SortOrder.ASCENDING;
343    
344        /**
345         * Controls the order in which the rows are traversed when rendering the
346         * data items.
347         */
348        private SortOrder rowRenderingOrder = SortOrder.ASCENDING;
349    
350        /**
351         * A flag that controls whether the grid-lines for the domain axis are
352         * visible.
353         */
354        private boolean domainGridlinesVisible;
355    
356        /** The position of the domain gridlines relative to the category. */
357        private CategoryAnchor domainGridlinePosition;
358    
359        /** The stroke used to draw the domain grid-lines. */
360        private transient Stroke domainGridlineStroke;
361    
362        /** The paint used to draw the domain  grid-lines. */
363        private transient Paint domainGridlinePaint;
364    
365        /**
366         * A flag that controls whether the grid-lines for the range axis are
367         * visible.
368         */
369        private boolean rangeGridlinesVisible;
370    
371        /** The stroke used to draw the range axis grid-lines. */
372        private transient Stroke rangeGridlineStroke;
373    
374        /** The paint used to draw the range axis grid-lines. */
375        private transient Paint rangeGridlinePaint;
376    
377        /** The anchor value. */
378        private double anchorValue;
379    
380        /**
381         * The index for the dataset that the crosshairs are linked to (this
382         * determines which axes the crosshairs are plotted against).
383         *
384         * @since 1.0.11
385         */
386        private int crosshairDatasetIndex;
387    
388        /**
389         * A flag that controls the visibility of the domain crosshair.
390         *
391         * @since 1.0.11
392         */
393        private boolean domainCrosshairVisible;
394    
395        /**
396         * The row key for the crosshair point.
397         *
398         * @since 1.0.11
399         */
400        private Comparable domainCrosshairRowKey;
401    
402        /**
403         * The column key for the crosshair point.
404         *
405         * @since 1.0.11
406         */
407        private Comparable domainCrosshairColumnKey;
408    
409        /**
410         * The stroke used to draw the domain crosshair if it is visible.
411         *
412         * @since 1.0.11
413         */
414        private transient Stroke domainCrosshairStroke;
415    
416        /**
417         * The paint used to draw the domain crosshair if it is visible.
418         *
419         * @since 1.0.11
420         */
421        private transient Paint domainCrosshairPaint;
422    
423        /** A flag that controls whether or not a range crosshair is drawn. */
424        private boolean rangeCrosshairVisible;
425    
426        /** The range crosshair value. */
427        private double rangeCrosshairValue;
428    
429        /** The pen/brush used to draw the crosshair (if any). */
430        private transient Stroke rangeCrosshairStroke;
431    
432        /** The color used to draw the crosshair (if any). */
433        private transient Paint rangeCrosshairPaint;
434    
435        /**
436         * A flag that controls whether or not the crosshair locks onto actual
437         * data points.
438         */
439        private boolean rangeCrosshairLockedOnData = true;
440    
441        /** A map containing lists of markers for the domain axes. */
442        private Map foregroundDomainMarkers;
443    
444        /** A map containing lists of markers for the domain axes. */
445        private Map backgroundDomainMarkers;
446    
447        /** A map containing lists of markers for the range axes. */
448        private Map foregroundRangeMarkers;
449    
450        /** A map containing lists of markers for the range axes. */
451        private Map backgroundRangeMarkers;
452    
453        /**
454         * A (possibly empty) list of annotations for the plot.  The list should
455         * be initialised in the constructor and never allowed to be
456         * <code>null</code>.
457         */
458        private List annotations;
459    
460        /**
461         * The weight for the plot (only relevant when the plot is used as a subplot
462         * within a combined plot).
463         */
464        private int weight;
465    
466        /** The fixed space for the domain axis. */
467        private AxisSpace fixedDomainAxisSpace;
468    
469        /** The fixed space for the range axis. */
470        private AxisSpace fixedRangeAxisSpace;
471    
472        /**
473         * An optional collection of legend items that can be returned by the
474         * getLegendItems() method.
475         */
476        private LegendItemCollection fixedLegendItems;
477    
478        /**
479         * Default constructor.
480         */
481        public CategoryPlot() {
482            this(null, null, null, null);
483        }
484    
485        /**
486         * Creates a new plot.
487         *
488         * @param dataset  the dataset (<code>null</code> permitted).
489         * @param domainAxis  the domain axis (<code>null</code> permitted).
490         * @param rangeAxis  the range axis (<code>null</code> permitted).
491         * @param renderer  the item renderer (<code>null</code> permitted).
492         *
493         */
494        public CategoryPlot(CategoryDataset dataset,
495                            CategoryAxis domainAxis,
496                            ValueAxis rangeAxis,
497                            CategoryItemRenderer renderer) {
498    
499            super();
500    
501            this.orientation = PlotOrientation.VERTICAL;
502    
503            // allocate storage for dataset, axes and renderers
504            this.domainAxes = new ObjectList();
505            this.domainAxisLocations = new ObjectList();
506            this.rangeAxes = new ObjectList();
507            this.rangeAxisLocations = new ObjectList();
508    
509            this.datasetToDomainAxesMap = new TreeMap();
510            this.datasetToRangeAxesMap = new TreeMap();
511    
512            this.renderers = new ObjectList();
513    
514            this.datasets = new ObjectList();
515            this.datasets.set(0, dataset);
516            if (dataset != null) {
517                dataset.addChangeListener(this);
518            }
519    
520            this.axisOffset = RectangleInsets.ZERO_INSETS;
521    
522            setDomainAxisLocation(AxisLocation.BOTTOM_OR_LEFT, false);
523            setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false);
524    
525            this.renderers.set(0, renderer);
526            if (renderer != null) {
527                renderer.setPlot(this);
528                renderer.addChangeListener(this);
529            }
530    
531            this.domainAxes.set(0, domainAxis);
532            this.mapDatasetToDomainAxis(0, 0);
533            if (domainAxis != null) {
534                domainAxis.setPlot(this);
535                domainAxis.addChangeListener(this);
536            }
537            this.drawSharedDomainAxis = false;
538    
539            this.rangeAxes.set(0, rangeAxis);
540            this.mapDatasetToRangeAxis(0, 0);
541            if (rangeAxis != null) {
542                rangeAxis.setPlot(this);
543                rangeAxis.addChangeListener(this);
544            }
545    
546            configureDomainAxes();
547            configureRangeAxes();
548    
549            this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE;
550            this.domainGridlinePosition = CategoryAnchor.MIDDLE;
551            this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;
552            this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;
553    
554            this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE;
555            this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;
556            this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;
557    
558            this.foregroundDomainMarkers = new HashMap();
559            this.backgroundDomainMarkers = new HashMap();
560            this.foregroundRangeMarkers = new HashMap();
561            this.backgroundRangeMarkers = new HashMap();
562    
563            Marker baseline = new ValueMarker(0.0, new Color(0.8f, 0.8f, 0.8f,
564                    0.5f), new BasicStroke(1.0f), new Color(0.85f, 0.85f, 0.95f,
565                    0.5f), new BasicStroke(1.0f), 0.6f);
566            addRangeMarker(baseline, Layer.BACKGROUND);
567    
568            this.anchorValue = 0.0;
569    
570            this.domainCrosshairVisible = false;
571            this.domainCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
572            this.domainCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
573    
574            this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE;
575            this.rangeCrosshairValue = 0.0;
576            this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE;
577            this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT;
578    
579            this.annotations = new java.util.ArrayList();
580    
581        }
582    
583        /**
584         * Returns a string describing the type of plot.
585         *
586         * @return The type.
587         */
588        public String getPlotType() {
589            return localizationResources.getString("Category_Plot");
590        }
591    
592        /**
593         * Returns the orientation of the plot.
594         *
595         * @return The orientation of the plot (never <code>null</code>).
596         *
597         * @see #setOrientation(PlotOrientation)
598         */
599        public PlotOrientation getOrientation() {
600            return this.orientation;
601        }
602    
603        /**
604         * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to
605         * all registered listeners.
606         *
607         * @param orientation  the orientation (<code>null</code> not permitted).
608         *
609         * @see #getOrientation()
610         */
611        public void setOrientation(PlotOrientation orientation) {
612            if (orientation == null) {
613                throw new IllegalArgumentException("Null 'orientation' argument.");
614            }
615            this.orientation = orientation;
616            fireChangeEvent();
617        }
618    
619        /**
620         * Returns the axis offset.
621         *
622         * @return The axis offset (never <code>null</code>).
623         *
624         * @see #setAxisOffset(RectangleInsets)
625         */
626        public RectangleInsets getAxisOffset() {
627            return this.axisOffset;
628        }
629    
630        /**
631         * Sets the axis offsets (gap between the data area and the axes) and
632         * sends a {@link PlotChangeEvent} to all registered listeners.
633         *
634         * @param offset  the offset (<code>null</code> not permitted).
635         *
636         * @see #getAxisOffset()
637         */
638        public void setAxisOffset(RectangleInsets offset) {
639            if (offset == null) {
640                throw new IllegalArgumentException("Null 'offset' argument.");
641            }
642            this.axisOffset = offset;
643            fireChangeEvent();
644        }
645    
646        /**
647         * Returns the domain axis for the plot.  If the domain axis for this plot
648         * is <code>null</code>, then the method will return the parent plot's
649         * domain axis (if there is a parent plot).
650         *
651         * @return The domain axis (<code>null</code> permitted).
652         *
653         * @see #setDomainAxis(CategoryAxis)
654         */
655        public CategoryAxis getDomainAxis() {
656            return getDomainAxis(0);
657        }
658    
659        /**
660         * Returns a domain axis.
661         *
662         * @param index  the axis index.
663         *
664         * @return The axis (<code>null</code> possible).
665         *
666         * @see #setDomainAxis(int, CategoryAxis)
667         */
668        public CategoryAxis getDomainAxis(int index) {
669            CategoryAxis result = null;
670            if (index < this.domainAxes.size()) {
671                result = (CategoryAxis) this.domainAxes.get(index);
672            }
673            if (result == null) {
674                Plot parent = getParent();
675                if (parent instanceof CategoryPlot) {
676                    CategoryPlot cp = (CategoryPlot) parent;
677                    result = cp.getDomainAxis(index);
678                }
679            }
680            return result;
681        }
682    
683        /**
684         * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to
685         * all registered listeners.
686         *
687         * @param axis  the axis (<code>null</code> permitted).
688         *
689         * @see #getDomainAxis()
690         */
691        public void setDomainAxis(CategoryAxis axis) {
692            setDomainAxis(0, axis);
693        }
694    
695        /**
696         * Sets a domain axis and sends a {@link PlotChangeEvent} to all
697         * registered listeners.
698         *
699         * @param index  the axis index.
700         * @param axis  the axis (<code>null</code> permitted).
701         *
702         * @see #getDomainAxis(int)
703         */
704        public void setDomainAxis(int index, CategoryAxis axis) {
705            setDomainAxis(index, axis, true);
706        }
707    
708        /**
709         * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
710         * all registered listeners.
711         *
712         * @param index  the axis index.
713         * @param axis  the axis (<code>null</code> permitted).
714         * @param notify  notify listeners?
715         */
716        public void setDomainAxis(int index, CategoryAxis axis, boolean notify) {
717            CategoryAxis existing = (CategoryAxis) this.domainAxes.get(index);
718            if (existing != null) {
719                existing.removeChangeListener(this);
720            }
721            if (axis != null) {
722                axis.setPlot(this);
723            }
724            this.domainAxes.set(index, axis);
725            if (axis != null) {
726                axis.configure();
727                axis.addChangeListener(this);
728            }
729            if (notify) {
730                fireChangeEvent();
731            }
732        }
733    
734        /**
735         * Sets the domain axes for this plot and sends a {@link PlotChangeEvent}
736         * to all registered listeners.
737         *
738         * @param axes  the axes (<code>null</code> not permitted).
739         *
740         * @see #setRangeAxes(ValueAxis[])
741         */
742        public void setDomainAxes(CategoryAxis[] axes) {
743            for (int i = 0; i < axes.length; i++) {
744                setDomainAxis(i, axes[i], false);
745            }
746            fireChangeEvent();
747        }
748    
749        /**
750         * Returns the index of the specified axis, or <code>-1</code> if the axis
751         * is not assigned to the plot.
752         *
753         * @param axis  the axis (<code>null</code> not permitted).
754         *
755         * @return The axis index.
756         *
757         * @see #getDomainAxis(int)
758         * @see #getRangeAxisIndex(ValueAxis)
759         *
760         * @since 1.0.3
761         */
762        public int getDomainAxisIndex(CategoryAxis axis) {
763            if (axis == null) {
764                throw new IllegalArgumentException("Null 'axis' argument.");
765            }
766            return this.domainAxes.indexOf(axis);
767        }
768    
769        /**
770         * Returns the domain axis location for the primary domain axis.
771         *
772         * @return The location (never <code>null</code>).
773         *
774         * @see #getRangeAxisLocation()
775         */
776        public AxisLocation getDomainAxisLocation() {
777            return getDomainAxisLocation(0);
778        }
779    
780        /**
781         * Returns the location for a domain axis.
782         *
783         * @param index  the axis index.
784         *
785         * @return The location.
786         *
787         * @see #setDomainAxisLocation(int, AxisLocation)
788         */
789        public AxisLocation getDomainAxisLocation(int index) {
790            AxisLocation result = null;
791            if (index < this.domainAxisLocations.size()) {
792                result = (AxisLocation) this.domainAxisLocations.get(index);
793            }
794            if (result == null) {
795                result = AxisLocation.getOpposite(getDomainAxisLocation(0));
796            }
797            return result;
798        }
799    
800        /**
801         * Sets the location of the domain axis and sends a {@link PlotChangeEvent}
802         * to all registered listeners.
803         *
804         * @param location  the axis location (<code>null</code> not permitted).
805         *
806         * @see #getDomainAxisLocation()
807         * @see #setDomainAxisLocation(int, AxisLocation)
808         */
809        public void setDomainAxisLocation(AxisLocation location) {
810            // delegate...
811            setDomainAxisLocation(0, location, true);
812        }
813    
814        /**
815         * Sets the location of the domain axis and, if requested, sends a
816         * {@link PlotChangeEvent} to all registered listeners.
817         *
818         * @param location  the axis location (<code>null</code> not permitted).
819         * @param notify  a flag that controls whether listeners are notified.
820         */
821        public void setDomainAxisLocation(AxisLocation location, boolean notify) {
822            // delegate...
823            setDomainAxisLocation(0, location, notify);
824        }
825    
826        /**
827         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
828         * to all registered listeners.
829         *
830         * @param index  the axis index.
831         * @param location  the location.
832         *
833         * @see #getDomainAxisLocation(int)
834         * @see #setRangeAxisLocation(int, AxisLocation)
835         */
836        public void setDomainAxisLocation(int index, AxisLocation location) {
837            // delegate...
838            setDomainAxisLocation(index, location, true);
839        }
840    
841        /**
842         * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
843         * to all registered listeners.
844         *
845         * @param index  the axis index.
846         * @param location  the location.
847         * @param notify  notify listeners?
848         *
849         * @since 1.0.5
850         *
851         * @see #getDomainAxisLocation(int)
852         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
853         */
854        public void setDomainAxisLocation(int index, AxisLocation location,
855                boolean notify) {
856            if (index == 0 && location == null) {
857                throw new IllegalArgumentException(
858                        "Null 'location' for index 0 not permitted.");
859            }
860            this.domainAxisLocations.set(index, location);
861            if (notify) {
862                fireChangeEvent();
863            }
864        }
865    
866        /**
867         * Returns the domain axis edge.  This is derived from the axis location
868         * and the plot orientation.
869         *
870         * @return The edge (never <code>null</code>).
871         */
872        public RectangleEdge getDomainAxisEdge() {
873            return getDomainAxisEdge(0);
874        }
875    
876        /**
877         * Returns the edge for a domain axis.
878         *
879         * @param index  the axis index.
880         *
881         * @return The edge (never <code>null</code>).
882         */
883        public RectangleEdge getDomainAxisEdge(int index) {
884            RectangleEdge result = null;
885            AxisLocation location = getDomainAxisLocation(index);
886            if (location != null) {
887                result = Plot.resolveDomainAxisLocation(location, this.orientation);
888            }
889            else {
890                result = RectangleEdge.opposite(getDomainAxisEdge(0));
891            }
892            return result;
893        }
894    
895        /**
896         * Returns the number of domain axes.
897         *
898         * @return The axis count.
899         */
900        public int getDomainAxisCount() {
901            return this.domainAxes.size();
902        }
903    
904        /**
905         * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
906         * to all registered listeners.
907         */
908        public void clearDomainAxes() {
909            for (int i = 0; i < this.domainAxes.size(); i++) {
910                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
911                if (axis != null) {
912                    axis.removeChangeListener(this);
913                }
914            }
915            this.domainAxes.clear();
916            fireChangeEvent();
917        }
918    
919        /**
920         * Configures the domain axes.
921         */
922        public void configureDomainAxes() {
923            for (int i = 0; i < this.domainAxes.size(); i++) {
924                CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i);
925                if (axis != null) {
926                    axis.configure();
927                }
928            }
929        }
930    
931        /**
932         * Returns the range axis for the plot.  If the range axis for this plot is
933         * null, then the method will return the parent plot's range axis (if there
934         * is a parent plot).
935         *
936         * @return The range axis (possibly <code>null</code>).
937         */
938        public ValueAxis getRangeAxis() {
939            return getRangeAxis(0);
940        }
941    
942        /**
943         * Returns a range axis.
944         *
945         * @param index  the axis index.
946         *
947         * @return The axis (<code>null</code> possible).
948         */
949        public ValueAxis getRangeAxis(int index) {
950            ValueAxis result = null;
951            if (index < this.rangeAxes.size()) {
952                result = (ValueAxis) this.rangeAxes.get(index);
953            }
954            if (result == null) {
955                Plot parent = getParent();
956                if (parent instanceof CategoryPlot) {
957                    CategoryPlot cp = (CategoryPlot) parent;
958                    result = cp.getRangeAxis(index);
959                }
960            }
961            return result;
962        }
963    
964        /**
965         * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
966         * all registered listeners.
967         *
968         * @param axis  the axis (<code>null</code> permitted).
969         */
970        public void setRangeAxis(ValueAxis axis) {
971            setRangeAxis(0, axis);
972        }
973    
974        /**
975         * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
976         * listeners.
977         *
978         * @param index  the axis index.
979         * @param axis  the axis.
980         */
981        public void setRangeAxis(int index, ValueAxis axis) {
982            setRangeAxis(index, axis, true);
983        }
984    
985        /**
986         * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
987         * all registered listeners.
988         *
989         * @param index  the axis index.
990         * @param axis  the axis.
991         * @param notify  notify listeners?
992         */
993        public void setRangeAxis(int index, ValueAxis axis, boolean notify) {
994            ValueAxis existing = (ValueAxis) this.rangeAxes.get(index);
995            if (existing != null) {
996                existing.removeChangeListener(this);
997            }
998            if (axis != null) {
999                axis.setPlot(this);
1000            }
1001            this.rangeAxes.set(index, axis);
1002            if (axis != null) {
1003                axis.configure();
1004                axis.addChangeListener(this);
1005            }
1006            if (notify) {
1007                fireChangeEvent();
1008            }
1009        }
1010    
1011        /**
1012         * Sets the range axes for this plot and sends a {@link PlotChangeEvent}
1013         * to all registered listeners.
1014         *
1015         * @param axes  the axes (<code>null</code> not permitted).
1016         *
1017         * @see #setDomainAxes(CategoryAxis[])
1018         */
1019        public void setRangeAxes(ValueAxis[] axes) {
1020            for (int i = 0; i < axes.length; i++) {
1021                setRangeAxis(i, axes[i], false);
1022            }
1023            fireChangeEvent();
1024        }
1025    
1026        /**
1027         * Returns the index of the specified axis, or <code>-1</code> if the axis
1028         * is not assigned to the plot.
1029         *
1030         * @param axis  the axis (<code>null</code> not permitted).
1031         *
1032         * @return The axis index.
1033         *
1034         * @see #getRangeAxis(int)
1035         * @see #getDomainAxisIndex(CategoryAxis)
1036         *
1037         * @since 1.0.7
1038         */
1039        public int getRangeAxisIndex(ValueAxis axis) {
1040            if (axis == null) {
1041                throw new IllegalArgumentException("Null 'axis' argument.");
1042            }
1043            int result = this.rangeAxes.indexOf(axis);
1044            if (result < 0) { // try the parent plot
1045                Plot parent = getParent();
1046                if (parent instanceof CategoryPlot) {
1047                    CategoryPlot p = (CategoryPlot) parent;
1048                    result = p.getRangeAxisIndex(axis);
1049                }
1050            }
1051            return result;
1052        }
1053    
1054        /**
1055         * Returns the range axis location.
1056         *
1057         * @return The location (never <code>null</code>).
1058         */
1059        public AxisLocation getRangeAxisLocation() {
1060            return getRangeAxisLocation(0);
1061        }
1062    
1063        /**
1064         * Returns the location for a range axis.
1065         *
1066         * @param index  the axis index.
1067         *
1068         * @return The location.
1069         *
1070         * @see #setRangeAxisLocation(int, AxisLocation)
1071         */
1072        public AxisLocation getRangeAxisLocation(int index) {
1073            AxisLocation result = null;
1074            if (index < this.rangeAxisLocations.size()) {
1075                result = (AxisLocation) this.rangeAxisLocations.get(index);
1076            }
1077            if (result == null) {
1078                result = AxisLocation.getOpposite(getRangeAxisLocation(0));
1079            }
1080            return result;
1081        }
1082    
1083        /**
1084         * Sets the location of the range axis and sends a {@link PlotChangeEvent}
1085         * to all registered listeners.
1086         *
1087         * @param location  the location (<code>null</code> not permitted).
1088         *
1089         * @see #setRangeAxisLocation(AxisLocation, boolean)
1090         * @see #setDomainAxisLocation(AxisLocation)
1091         */
1092        public void setRangeAxisLocation(AxisLocation location) {
1093            // defer argument checking...
1094            setRangeAxisLocation(location, true);
1095        }
1096    
1097        /**
1098         * Sets the location of the range axis and, if requested, sends a
1099         * {@link PlotChangeEvent} to all registered listeners.
1100         *
1101         * @param location  the location (<code>null</code> not permitted).
1102         * @param notify  notify listeners?
1103         *
1104         * @see #setDomainAxisLocation(AxisLocation, boolean)
1105         */
1106        public void setRangeAxisLocation(AxisLocation location, boolean notify) {
1107            setRangeAxisLocation(0, location, notify);
1108        }
1109    
1110        /**
1111         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
1112         * to all registered listeners.
1113         *
1114         * @param index  the axis index.
1115         * @param location  the location.
1116         *
1117         * @see #getRangeAxisLocation(int)
1118         * @see #setRangeAxisLocation(int, AxisLocation, boolean)
1119         */
1120        public void setRangeAxisLocation(int index, AxisLocation location) {
1121            setRangeAxisLocation(index, location, true);
1122        }
1123    
1124        /**
1125         * Sets the location for a range axis and sends a {@link PlotChangeEvent}
1126         * to all registered listeners.
1127         *
1128         * @param index  the axis index.
1129         * @param location  the location.
1130         * @param notify  notify listeners?
1131         *
1132         * @see #getRangeAxisLocation(int)
1133         * @see #setDomainAxisLocation(int, AxisLocation, boolean)
1134         */
1135        public void setRangeAxisLocation(int index, AxisLocation location,
1136                                         boolean notify) {
1137            if (index == 0 && location == null) {
1138                throw new IllegalArgumentException(
1139                        "Null 'location' for index 0 not permitted.");
1140            }
1141            this.rangeAxisLocations.set(index, location);
1142            if (notify) {
1143                fireChangeEvent();
1144            }
1145        }
1146    
1147        /**
1148         * Returns the edge where the primary range axis is located.
1149         *
1150         * @return The edge (never <code>null</code>).
1151         */
1152        public RectangleEdge getRangeAxisEdge() {
1153            return getRangeAxisEdge(0);
1154        }
1155    
1156        /**
1157         * Returns the edge for a range axis.
1158         *
1159         * @param index  the axis index.
1160         *
1161         * @return The edge.
1162         */
1163        public RectangleEdge getRangeAxisEdge(int index) {
1164            AxisLocation location = getRangeAxisLocation(index);
1165            RectangleEdge result = Plot.resolveRangeAxisLocation(location,
1166                    this.orientation);
1167            if (result == null) {
1168                result = RectangleEdge.opposite(getRangeAxisEdge(0));
1169            }
1170            return result;
1171        }
1172    
1173        /**
1174         * Returns the number of range axes.
1175         *
1176         * @return The axis count.
1177         */
1178        public int getRangeAxisCount() {
1179            return this.rangeAxes.size();
1180        }
1181    
1182        /**
1183         * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
1184         * to all registered listeners.
1185         */
1186        public void clearRangeAxes() {
1187            for (int i = 0; i < this.rangeAxes.size(); i++) {
1188                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1189                if (axis != null) {
1190                    axis.removeChangeListener(this);
1191                }
1192            }
1193            this.rangeAxes.clear();
1194            fireChangeEvent();
1195        }
1196    
1197        /**
1198         * Configures the range axes.
1199         */
1200        public void configureRangeAxes() {
1201            for (int i = 0; i < this.rangeAxes.size(); i++) {
1202                ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
1203                if (axis != null) {
1204                    axis.configure();
1205                }
1206            }
1207        }
1208    
1209        /**
1210         * Returns the primary dataset for the plot.
1211         *
1212         * @return The primary dataset (possibly <code>null</code>).
1213         *
1214         * @see #setDataset(CategoryDataset)
1215         */
1216        public CategoryDataset getDataset() {
1217            return getDataset(0);
1218        }
1219    
1220        /**
1221         * Returns the dataset at the given index.
1222         *
1223         * @param index  the dataset index.
1224         *
1225         * @return The dataset (possibly <code>null</code>).
1226         *
1227         * @see #setDataset(int, CategoryDataset)
1228         */
1229        public CategoryDataset getDataset(int index) {
1230            CategoryDataset result = null;
1231            if (this.datasets.size() > index) {
1232                result = (CategoryDataset) this.datasets.get(index);
1233            }
1234            return result;
1235        }
1236    
1237        /**
1238         * Sets the dataset for the plot, replacing the existing dataset, if there
1239         * is one.  This method also calls the
1240         * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the
1241         * axis ranges if necessary and sends a {@link PlotChangeEvent} to all
1242         * registered listeners.
1243         *
1244         * @param dataset  the dataset (<code>null</code> permitted).
1245         *
1246         * @see #getDataset()
1247         */
1248        public void setDataset(CategoryDataset dataset) {
1249            setDataset(0, dataset);
1250        }
1251    
1252        /**
1253         * Sets a dataset for the plot.
1254         *
1255         * @param index  the dataset index.
1256         * @param dataset  the dataset (<code>null</code> permitted).
1257         *
1258         * @see #getDataset(int)
1259         */
1260        public void setDataset(int index, CategoryDataset dataset) {
1261    
1262            CategoryDataset existing = (CategoryDataset) this.datasets.get(index);
1263            if (existing != null) {
1264                existing.removeChangeListener(this);
1265            }
1266            this.datasets.set(index, dataset);
1267            if (dataset != null) {
1268                dataset.addChangeListener(this);
1269            }
1270    
1271            // send a dataset change event to self...
1272            DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
1273            datasetChanged(event);
1274    
1275        }
1276    
1277        /**
1278         * Returns the number of datasets.
1279         *
1280         * @return The number of datasets.
1281         *
1282         * @since 1.0.2
1283         */
1284        public int getDatasetCount() {
1285            return this.datasets.size();
1286        }
1287    
1288        /**
1289         * Returns the index of the specified dataset, or <code>-1</code> if the
1290         * dataset does not belong to the plot.
1291         *
1292         * @param dataset  the dataset (<code>null</code> not permitted).
1293         *
1294         * @return The index.
1295         *
1296         * @since 1.0.11
1297         */
1298        public int indexOf(CategoryDataset dataset) {
1299            int result = -1;
1300            for (int i = 0; i < this.datasets.size(); i++) {
1301                if (dataset == this.datasets.get(i)) {
1302                    result = i;
1303                    break;
1304                }
1305            }
1306            return result;
1307        }
1308    
1309        /**
1310         * Maps a dataset to a particular domain axis.
1311         *
1312         * @param index  the dataset index (zero-based).
1313         * @param axisIndex  the axis index (zero-based).
1314         *
1315         * @see #getDomainAxisForDataset(int)
1316         */
1317        public void mapDatasetToDomainAxis(int index, int axisIndex) {
1318            List axisIndices = new java.util.ArrayList(1);
1319            axisIndices.add(new Integer(axisIndex));
1320            mapDatasetToDomainAxes(index, axisIndices);
1321        }
1322    
1323        /**
1324         * Maps the specified dataset to the axes in the list.  Note that the
1325         * conversion of data values into Java2D space is always performed using
1326         * the first axis in the list.
1327         *
1328         * @param index  the dataset index (zero-based).
1329         * @param axisIndices  the axis indices (<code>null</code> permitted).
1330         *
1331         * @since 1.0.12
1332         */
1333        public void mapDatasetToDomainAxes(int index, List axisIndices) {
1334            if (index < 0) {
1335                throw new IllegalArgumentException("Requires 'index' >= 0.");
1336            }
1337            checkAxisIndices(axisIndices);
1338            Integer key = new Integer(index);
1339            this.datasetToDomainAxesMap.put(key, new ArrayList(axisIndices));
1340            // fake a dataset change event to update axes...
1341            datasetChanged(new DatasetChangeEvent(this, getDataset(index)));
1342        }
1343    
1344        /**
1345         * This method is used to perform argument checking on the list of
1346         * axis indices passed to mapDatasetToDomainAxes() and
1347         * mapDatasetToRangeAxes().
1348         *
1349         * @param indices  the list of indices (<code>null</code> permitted).
1350         */
1351        private void checkAxisIndices(List indices) {
1352            // axisIndices can be:
1353            // 1.  null;
1354            // 2.  non-empty, containing only Integer objects that are unique.
1355            if (indices == null) {
1356                return;  // OK
1357            }
1358            int count = indices.size();
1359            if (count == 0) {
1360                throw new IllegalArgumentException("Empty list not permitted.");
1361            }
1362            HashSet set = new HashSet();
1363            for (int i = 0; i < count; i++) {
1364                Object item = indices.get(i);
1365                if (!(item instanceof Integer)) {
1366                    throw new IllegalArgumentException(
1367                            "Indices must be Integer instances.");
1368                }
1369                if (set.contains(item)) {
1370                    throw new IllegalArgumentException("Indices must be unique.");
1371                }
1372                set.add(item);
1373            }
1374        }
1375    
1376        /**
1377         * Returns the domain axis for a dataset.  You can change the axis for a
1378         * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method.
1379         *
1380         * @param index  the dataset index.
1381         *
1382         * @return The domain axis.
1383         *
1384         * @see #mapDatasetToDomainAxis(int, int)
1385         */
1386        public CategoryAxis getDomainAxisForDataset(int index) {
1387            if (index < 0) {
1388                throw new IllegalArgumentException("Negative 'index'.");
1389            }
1390            CategoryAxis axis = null;
1391            List axisIndices = (List) this.datasetToDomainAxesMap.get(
1392                    new Integer(index));
1393            if (axisIndices != null) {
1394                // the first axis in the list is used for data <--> Java2D
1395                Integer axisIndex = (Integer) axisIndices.get(0);
1396                axis = getDomainAxis(axisIndex.intValue());
1397            }
1398            else {
1399                axis = getDomainAxis(0);
1400            }
1401            return axis;
1402        }
1403    
1404        /**
1405         * Maps a dataset to a particular range axis.
1406         *
1407         * @param index  the dataset index (zero-based).
1408         * @param axisIndex  the axis index (zero-based).
1409         *
1410         * @see #getRangeAxisForDataset(int)
1411         */
1412        public void mapDatasetToRangeAxis(int index, int axisIndex) {
1413            List axisIndices = new java.util.ArrayList(1);
1414            axisIndices.add(new Integer(axisIndex));
1415            mapDatasetToRangeAxes(index, axisIndices);
1416        }
1417    
1418        /**
1419         * Maps the specified dataset to the axes in the list.  Note that the
1420         * conversion of data values into Java2D space is always performed using
1421         * the first axis in the list.
1422         *
1423         * @param index  the dataset index (zero-based).
1424         * @param axisIndices  the axis indices (<code>null</code> permitted).
1425         *
1426         * @since 1.0.12
1427         */
1428        public void mapDatasetToRangeAxes(int index, List axisIndices) {
1429            if (index < 0) {