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