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