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