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