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