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