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