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