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