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