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