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