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