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