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