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