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