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