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