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