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