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