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) {