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