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