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