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