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