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