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