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