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