001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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-2007, 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 * 036 * $Id: CategoryPlot.java,v 1.23.2.16 2007/03/13 11:38:12 mungady Exp $ 037 * 038 * Changes (from 21-Jun-2001) 039 * -------------------------- 040 * 21-Jun-2001 : Removed redundant JFreeChart parameter from constructors (DG); 041 * 21-Aug-2001 : Added standard header. Fixed DOS encoding problem (DG); 042 * 18-Sep-2001 : Updated header (DG); 043 * 15-Oct-2001 : Data source classes moved to com.jrefinery.data.* (DG); 044 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG); 045 * 23-Oct-2001 : Changed intro and trail gaps on bar plots to use percentage of 046 * available space rather than a fixed number of units (DG); 047 * 12-Dec-2001 : Changed constructors to protected (DG); 048 * 13-Dec-2001 : Added tooltips (DG); 049 * 16-Jan-2002 : Increased maximum intro and trail gap percents, plus added 050 * some argument checking code. Thanks to Taoufik Romdhane for 051 * suggesting this (DG); 052 * 05-Feb-2002 : Added accessor methods for the tooltip generator, incorporated 053 * alpha-transparency for Plot and subclasses (DG); 054 * 06-Mar-2002 : Updated import statements (DG); 055 * 14-Mar-2002 : Renamed BarPlot.java --> CategoryPlot.java, and changed code 056 * to use the CategoryItemRenderer interface (DG); 057 * 22-Mar-2002 : Dropped the getCategories() method (DG); 058 * 23-Apr-2002 : Moved the dataset from the JFreeChart class to the Plot 059 * class (DG); 060 * 29-Apr-2002 : New methods to support printing values at the end of bars, 061 * contributed by Jeremy Bowman (DG); 062 * 11-May-2002 : New methods for label visibility and overlaid plot support, 063 * contributed by Jeremy Bowman (DG); 064 * 06-Jun-2002 : Removed the tooltip generator, this is now stored with the 065 * renderer. Moved constants into the CategoryPlotConstants 066 * interface. Updated Javadoc comments (DG); 067 * 10-Jun-2002 : Overridden datasetChanged() method to update the upper and 068 * lower bound on the range axis (if necessary), updated 069 * Javadocs (DG); 070 * 25-Jun-2002 : Removed redundant imports (DG); 071 * 20-Aug-2002 : Changed the constructor for Marker (DG); 072 * 28-Aug-2002 : Added listener notification to setDomainAxis() and 073 * setRangeAxis() (DG); 074 * 23-Sep-2002 : Added getLegendItems() method and fixed errors reported by 075 * Checkstyle (DG); 076 * 28-Oct-2002 : Changes to the CategoryDataset interface (DG); 077 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG); 078 * 07-Nov-2002 : Renamed labelXXX as valueLabelXXX (DG); 079 * 18-Nov-2002 : Added grid settings for both domain and range axis (previously 080 * these were set in the axes) (DG); 081 * 19-Nov-2002 : Added axis location parameters to constructor (DG); 082 * 17-Jan-2003 : Moved to com.jrefinery.chart.plot package (DG); 083 * 14-Feb-2003 : Fixed bug in auto-range calculation for secondary axis (DG); 084 * 26-Mar-2003 : Implemented Serializable (DG); 085 * 02-May-2003 : Moved render() method up from subclasses. Added secondary 086 * range markers. Added an attribute to control the dataset 087 * rendering order. Added a drawAnnotations() method. Changed 088 * the axis location from an int to an AxisLocation (DG); 089 * 07-May-2003 : Merged HorizontalCategoryPlot and VerticalCategoryPlot into 090 * this class (DG); 091 * 02-Jun-2003 : Removed check for range axis compatibility (DG); 092 * 04-Jul-2003 : Added a domain gridline position attribute (DG); 093 * 21-Jul-2003 : Moved DrawingSupplier to Plot superclass (DG); 094 * 19-Aug-2003 : Added equals() method and implemented Cloneable (DG); 095 * 01-Sep-2003 : Fixed bug 797466 (no change event when secondary dataset 096 * changes) (DG); 097 * 02-Sep-2003 : Fixed bug 795209 (wrong dataset checked in render2 method) and 098 * 790407 (initialise method) (DG); 099 * 08-Sep-2003 : Added internationalization via use of properties 100 * resourceBundle (RFE 690236) (AL); 101 * 08-Sep-2003 : Fixed bug (wrong secondary range axis being used). Changed 102 * ValueAxis API (DG); 103 * 10-Sep-2003 : Fixed bug in setRangeAxis() method (DG); 104 * 15-Sep-2003 : Fixed two bugs in serialization, implemented 105 * PublicCloneable (DG); 106 * 23-Oct-2003 : Added event notification for changes to renderer (DG); 107 * 26-Nov-2003 : Fixed bug (849645) in clearRangeMarkers() method (DG); 108 * 03-Dec-2003 : Modified draw method to accept anchor (DG); 109 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 110 * 10-Mar-2004 : Fixed bug in axis range calculation when secondary renderer is 111 * stacked (DG); 112 * 12-May-2004 : Added fixed legend items (DG); 113 * 19-May-2004 : Added check for null legend item from renderer (DG); 114 * 02-Jun-2004 : Updated the DatasetRenderingOrder class (DG); 115 * 05-Nov-2004 : Renamed getDatasetsMappedToRangeAxis() 116 * --> datasetsMappedToRangeAxis(), and ensured that returned 117 * list doesn't contain null datasets (DG); 118 * 12-Nov-2004 : Implemented new Zoomable interface (DG); 119 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() in 120 * CategoryItemRenderer (DG); 121 * 04-May-2005 : Fixed serialization of range markers (DG); 122 * 05-May-2005 : Updated draw() method parameters (DG); 123 * 20-May-2005 : Added setDomainAxes() and setRangeAxes() methods, as per 124 * RFE 1183100 (DG); 125 * 01-Jun-2005 : Upon deserialization, register plot as a listener with its 126 * axes, dataset(s) and renderer(s) - see patch 1209475 (DG); 127 * 02-Jun-2005 : Added support for domain markers (DG); 128 * 06-Jun-2005 : Fixed equals() method for use with GradientPaint (DG); 129 * 09-Jun-2005 : Added setRenderers(), as per RFE 1183100 (DG); 130 * 16-Jun-2005 : Added getDomainAxisCount() and getRangeAxisCount() methods, to 131 * match XYPlot (see RFE 1220495) (DG); 132 * ------------- JFREECHART 1.0.x --------------------------------------------- 133 * 11-Jan-2006 : Added configureRangeAxes() to rendererChanged(), since the 134 * renderer might influence the axis range (DG); 135 * 27-Jan-2006 : Added various null argument checks (DG); 136 * 18-Aug-2006 : Added getDatasetCount() method, plus a fix for bug drawing 137 * category labels, thanks to Adriaan Joubert (1277726) (DG); 138 * 05-Sep-2006 : Added MarkerChangeEvent support (DG); 139 * 30-Oct-2006 : Added getDomainAxisIndex(), datasetsMappedToDomainAxis() and 140 * getCategoriesForAxis() methods (DG); 141 * 22-Nov-2006 : Fire PlotChangeEvent from setColumnRenderingOrder() and 142 * setRowRenderingOrder() (DG); 143 * 29-Nov-2006 : Fix for bug 1605207 (IntervalMarker exceeds bounds of data 144 * area) (DG); 145 * 26-Feb-2007 : Fix for bug 1669218 (setDomainAxisLocation() notify argument 146 * ignored) (DG); 147 * 13-Mar-2007 : Added null argument checks for setRangeCrosshairPaint() and 148 * setRangeCrosshairStroke(), fixed clipping for 149 * anntotations (DG); 150 * 151 */ 152 153 package org.jfree.chart.plot; 154 155 import java.awt.AlphaComposite; 156 import java.awt.BasicStroke; 157 import java.awt.Color; 158 import java.awt.Composite; 159 import java.awt.Font; 160 import java.awt.Graphics2D; 161 import java.awt.Paint; 162 import java.awt.Shape; 163 import java.awt.Stroke; 164 import java.awt.geom.Line2D; 165 import java.awt.geom.Point2D; 166 import java.awt.geom.Rectangle2D; 167 import java.io.IOException; 168 import java.io.ObjectInputStream; 169 import java.io.ObjectOutputStream; 170 import java.io.Serializable; 171 import java.util.ArrayList; 172 import java.util.Collection; 173 import java.util.Collections; 174 import java.util.HashMap; 175 import java.util.Iterator; 176 import java.util.List; 177 import java.util.Map; 178 import java.util.ResourceBundle; 179 import java.util.Set; 180 181 import org.jfree.chart.LegendItem; 182 import org.jfree.chart.LegendItemCollection; 183 import org.jfree.chart.annotations.CategoryAnnotation; 184 import org.jfree.chart.axis.Axis; 185 import org.jfree.chart.axis.AxisCollection; 186 import org.jfree.chart.axis.AxisLocation; 187 import org.jfree.chart.axis.AxisSpace; 188 import org.jfree.chart.axis.AxisState; 189 import org.jfree.chart.axis.CategoryAnchor; 190 import org.jfree.chart.axis.CategoryAxis; 191 import org.jfree.chart.axis.ValueAxis; 192 import org.jfree.chart.axis.ValueTick; 193 import org.jfree.chart.event.ChartChangeEventType; 194 import org.jfree.chart.event.PlotChangeEvent; 195 import org.jfree.chart.event.RendererChangeEvent; 196 import org.jfree.chart.event.RendererChangeListener; 197 import org.jfree.chart.renderer.category.CategoryItemRenderer; 198 import org.jfree.chart.renderer.category.CategoryItemRendererState; 199 import org.jfree.data.Range; 200 import org.jfree.data.category.CategoryDataset; 201 import org.jfree.data.general.Dataset; 202 import org.jfree.data.general.DatasetChangeEvent; 203 import org.jfree.data.general.DatasetUtilities; 204 import org.jfree.io.SerialUtilities; 205 import org.jfree.ui.Layer; 206 import org.jfree.ui.RectangleEdge; 207 import org.jfree.ui.RectangleInsets; 208 import org.jfree.util.ObjectList; 209 import org.jfree.util.ObjectUtilities; 210 import org.jfree.util.PaintUtilities; 211 import org.jfree.util.PublicCloneable; 212 import org.jfree.util.SortOrder; 213 214 /** 215 * A general plotting class that uses data from a {@link CategoryDataset} and 216 * renders each data item using a {@link CategoryItemRenderer}. 217 */ 218 public class CategoryPlot extends Plot 219 implements ValueAxisPlot, 220 Zoomable, 221 RendererChangeListener, 222 Cloneable, PublicCloneable, Serializable { 223 224 /** For serialization. */ 225 private static final long serialVersionUID = -3537691700434728188L; 226 227 /** 228 * The default visibility of the grid lines plotted against the domain 229 * axis. 230 */ 231 public static final boolean DEFAULT_DOMAIN_GRIDLINES_VISIBLE = false; 232 233 /** 234 * The default visibility of the grid lines plotted against the range 235 * axis. 236 */ 237 public static final boolean DEFAULT_RANGE_GRIDLINES_VISIBLE = true; 238 239 /** The default grid line stroke. */ 240 public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(0.5f, 241 BasicStroke.CAP_BUTT, 242 BasicStroke.JOIN_BEVEL, 243 0.0f, 244 new float[] {2.0f, 2.0f}, 245 0.0f); 246 247 /** The default grid line paint. */ 248 public static final Paint DEFAULT_GRIDLINE_PAINT = Color.lightGray; 249 250 /** The default value label font. */ 251 public static final Font DEFAULT_VALUE_LABEL_FONT 252 = new Font("SansSerif", Font.PLAIN, 10); 253 254 /** 255 * The default crosshair visibility. 256 * 257 * @since 1.0.5 258 */ 259 public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false; 260 261 /** 262 * The default crosshair stroke. 263 * 264 * @since 1.0.5 265 */ 266 public static final Stroke DEFAULT_CROSSHAIR_STROKE 267 = DEFAULT_GRIDLINE_STROKE; 268 269 /** 270 * The default crosshair paint. 271 * 272 * @since 1.0.5 273 */ 274 public static final Paint DEFAULT_CROSSHAIR_PAINT = Color.blue; 275 276 /** The resourceBundle for the localization. */ 277 protected static ResourceBundle localizationResources 278 = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle"); 279 280 /** The plot orientation. */ 281 private PlotOrientation orientation; 282 283 /** The offset between the data area and the axes. */ 284 private RectangleInsets axisOffset; 285 286 /** Storage for the domain axes. */ 287 private ObjectList domainAxes; 288 289 /** Storage for the domain axis locations. */ 290 private ObjectList domainAxisLocations; 291 292 /** 293 * A flag that controls whether or not the shared domain axis is drawn 294 * (only relevant when the plot is being used as a subplot). 295 */ 296 private boolean drawSharedDomainAxis; 297 298 /** Storage for the range axes. */ 299 private ObjectList rangeAxes; 300 301 /** Storage for the range axis locations. */ 302 private ObjectList rangeAxisLocations; 303 304 /** Storage for the datasets. */ 305 private ObjectList datasets; 306 307 /** Storage for keys that map datasets to domain axes. */ 308 private ObjectList datasetToDomainAxisMap; 309 310 /** Storage for keys that map datasets to range axes. */ 311 private ObjectList datasetToRangeAxisMap; 312 313 /** Storage for the renderers. */ 314 private ObjectList renderers; 315 316 /** The dataset rendering order. */ 317 private DatasetRenderingOrder renderingOrder 318 = DatasetRenderingOrder.REVERSE; 319 320 /** 321 * Controls the order in which the columns are traversed when rendering the 322 * data items. 323 */ 324 private SortOrder columnRenderingOrder = SortOrder.ASCENDING; 325 326 /** 327 * Controls the order in which the rows are traversed when rendering the 328 * data items. 329 */ 330 private SortOrder rowRenderingOrder = SortOrder.ASCENDING; 331 332 /** 333 * A flag that controls whether the grid-lines for the domain axis are 334 * visible. 335 */ 336 private boolean domainGridlinesVisible; 337 338 /** The position of the domain gridlines relative to the category. */ 339 private CategoryAnchor domainGridlinePosition; 340 341 /** The stroke used to draw the domain grid-lines. */ 342 private transient Stroke domainGridlineStroke; 343 344 /** The paint used to draw the domain grid-lines. */ 345 private transient Paint domainGridlinePaint; 346 347 /** 348 * A flag that controls whether the grid-lines for the range axis are 349 * visible. 350 */ 351 private boolean rangeGridlinesVisible; 352 353 /** The stroke used to draw the range axis grid-lines. */ 354 private transient Stroke rangeGridlineStroke; 355 356 /** The paint used to draw the range axis grid-lines. */ 357 private transient Paint rangeGridlinePaint; 358 359 /** The anchor value. */ 360 private double anchorValue; 361 362 /** A flag that controls whether or not a range crosshair is drawn. */ 363 private boolean rangeCrosshairVisible; 364 365 /** The range crosshair value. */ 366 private double rangeCrosshairValue; 367 368 /** The pen/brush used to draw the crosshair (if any). */ 369 private transient Stroke rangeCrosshairStroke; 370 371 /** The color used to draw the crosshair (if any). */ 372 private transient Paint rangeCrosshairPaint; 373 374 /** 375 * A flag that controls whether or not the crosshair locks onto actual 376 * data points. 377 */ 378 private boolean rangeCrosshairLockedOnData = true; 379 380 /** A map containing lists of markers for the domain axes. */ 381 private Map foregroundDomainMarkers; 382 383 /** A map containing lists of markers for the domain axes. */ 384 private Map backgroundDomainMarkers; 385 386 /** A map containing lists of markers for the range axes. */ 387 private Map foregroundRangeMarkers; 388 389 /** A map containing lists of markers for the range axes. */ 390 private Map backgroundRangeMarkers; 391 392 /** 393 * A (possibly empty) list of annotations for the plot. The list should 394 * be initialised in the constructor and never allowed to be 395 * <code>null</code>. 396 */ 397 private List annotations; 398 399 /** 400 * The weight for the plot (only relevant when the plot is used as a subplot 401 * within a combined plot). 402 */ 403 private int weight; 404 405 /** The fixed space for the domain axis. */ 406 private AxisSpace fixedDomainAxisSpace; 407 408 /** The fixed space for the range axis. */ 409 private AxisSpace fixedRangeAxisSpace; 410 411 /** 412 * An optional collection of legend items that can be returned by the 413 * getLegendItems() method. 414 */ 415 private LegendItemCollection fixedLegendItems; 416 417 /** 418 * Default constructor. 419 */ 420 public CategoryPlot() { 421 this(null, null, null, null); 422 } 423 424 /** 425 * Creates a new plot. 426 * 427 * @param dataset the dataset (<code>null</code> permitted). 428 * @param domainAxis the domain axis (<code>null</code> permitted). 429 * @param rangeAxis the range axis (<code>null</code> permitted). 430 * @param renderer the item renderer (<code>null</code> permitted). 431 * 432 */ 433 public CategoryPlot(CategoryDataset dataset, 434 CategoryAxis domainAxis, 435 ValueAxis rangeAxis, 436 CategoryItemRenderer renderer) { 437 438 super(); 439 440 this.orientation = PlotOrientation.VERTICAL; 441 442 // allocate storage for dataset, axes and renderers 443 this.domainAxes = new ObjectList(); 444 this.domainAxisLocations = new ObjectList(); 445 this.rangeAxes = new ObjectList(); 446 this.rangeAxisLocations = new ObjectList(); 447 448 this.datasetToDomainAxisMap = new ObjectList(); 449 this.datasetToRangeAxisMap = new ObjectList(); 450 451 this.renderers = new ObjectList(); 452 453 this.datasets = new ObjectList(); 454 this.datasets.set(0, dataset); 455 if (dataset != null) { 456 dataset.addChangeListener(this); 457 } 458 459 this.axisOffset = RectangleInsets.ZERO_INSETS; 460 461 setDomainAxisLocation(AxisLocation.BOTTOM_OR_LEFT, false); 462 setRangeAxisLocation(AxisLocation.TOP_OR_LEFT, false); 463 464 this.renderers.set(0, renderer); 465 if (renderer != null) { 466 renderer.setPlot(this); 467 renderer.addChangeListener(this); 468 } 469 470 this.domainAxes.set(0, domainAxis); 471 this.mapDatasetToDomainAxis(0, 0); 472 if (domainAxis != null) { 473 domainAxis.setPlot(this); 474 domainAxis.addChangeListener(this); 475 } 476 this.drawSharedDomainAxis = false; 477 478 this.rangeAxes.set(0, rangeAxis); 479 this.mapDatasetToRangeAxis(0, 0); 480 if (rangeAxis != null) { 481 rangeAxis.setPlot(this); 482 rangeAxis.addChangeListener(this); 483 } 484 485 configureDomainAxes(); 486 configureRangeAxes(); 487 488 this.domainGridlinesVisible = DEFAULT_DOMAIN_GRIDLINES_VISIBLE; 489 this.domainGridlinePosition = CategoryAnchor.MIDDLE; 490 this.domainGridlineStroke = DEFAULT_GRIDLINE_STROKE; 491 this.domainGridlinePaint = DEFAULT_GRIDLINE_PAINT; 492 493 this.rangeGridlinesVisible = DEFAULT_RANGE_GRIDLINES_VISIBLE; 494 this.rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE; 495 this.rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT; 496 497 this.foregroundDomainMarkers = new HashMap(); 498 this.backgroundDomainMarkers = new HashMap(); 499 this.foregroundRangeMarkers = new HashMap(); 500 this.backgroundRangeMarkers = new HashMap(); 501 502 Marker baseline = new ValueMarker(0.0, new Color(0.8f, 0.8f, 0.8f, 503 0.5f), new BasicStroke(1.0f), new Color(0.85f, 0.85f, 0.95f, 504 0.5f), new BasicStroke(1.0f), 0.6f); 505 addRangeMarker(baseline, Layer.BACKGROUND); 506 507 this.anchorValue = 0.0; 508 509 this.rangeCrosshairVisible = DEFAULT_CROSSHAIR_VISIBLE; 510 this.rangeCrosshairValue = 0.0; 511 this.rangeCrosshairStroke = DEFAULT_CROSSHAIR_STROKE; 512 this.rangeCrosshairPaint = DEFAULT_CROSSHAIR_PAINT; 513 514 this.annotations = new java.util.ArrayList(); 515 516 } 517 518 /** 519 * Returns a string describing the type of plot. 520 * 521 * @return The type. 522 */ 523 public String getPlotType() { 524 return localizationResources.getString("Category_Plot"); 525 } 526 527 /** 528 * Returns the orientation of the plot. 529 * 530 * @return The orientation of the plot (never <code>null</code>). 531 * 532 * @see #setOrientation(PlotOrientation) 533 */ 534 public PlotOrientation getOrientation() { 535 return this.orientation; 536 } 537 538 /** 539 * Sets the orientation for the plot and sends a {@link PlotChangeEvent} to 540 * all registered listeners. 541 * 542 * @param orientation the orientation (<code>null</code> not permitted). 543 * 544 * @see #getOrientation() 545 */ 546 public void setOrientation(PlotOrientation orientation) { 547 if (orientation == null) { 548 throw new IllegalArgumentException("Null 'orientation' argument."); 549 } 550 this.orientation = orientation; 551 notifyListeners(new PlotChangeEvent(this)); 552 } 553 554 /** 555 * Returns the axis offset. 556 * 557 * @return The axis offset (never <code>null</code>). 558 * 559 * @see #setAxisOffset(RectangleInsets) 560 */ 561 public RectangleInsets getAxisOffset() { 562 return this.axisOffset; 563 } 564 565 /** 566 * Sets the axis offsets (gap between the data area and the axes) and 567 * sends a {@link PlotChangeEvent} to all registered listeners. 568 * 569 * @param offset the offset (<code>null</code> not permitted). 570 * 571 * @see #getAxisOffset() 572 */ 573 public void setAxisOffset(RectangleInsets offset) { 574 if (offset == null) { 575 throw new IllegalArgumentException("Null 'offset' argument."); 576 } 577 this.axisOffset = offset; 578 notifyListeners(new PlotChangeEvent(this)); 579 } 580 581 /** 582 * Returns the domain axis for the plot. If the domain axis for this plot 583 * is <code>null</code>, then the method will return the parent plot's 584 * domain axis (if there is a parent plot). 585 * 586 * @return The domain axis (<code>null</code> permitted). 587 * 588 * @see #setDomainAxis(CategoryAxis) 589 */ 590 public CategoryAxis getDomainAxis() { 591 return getDomainAxis(0); 592 } 593 594 /** 595 * Returns a domain axis. 596 * 597 * @param index the axis index. 598 * 599 * @return The axis (<code>null</code> possible). 600 * 601 * @see #setDomainAxis(int, CategoryAxis) 602 */ 603 public CategoryAxis getDomainAxis(int index) { 604 CategoryAxis result = null; 605 if (index < this.domainAxes.size()) { 606 result = (CategoryAxis) this.domainAxes.get(index); 607 } 608 if (result == null) { 609 Plot parent = getParent(); 610 if (parent instanceof CategoryPlot) { 611 CategoryPlot cp = (CategoryPlot) parent; 612 result = cp.getDomainAxis(index); 613 } 614 } 615 return result; 616 } 617 618 /** 619 * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to 620 * all registered listeners. 621 * 622 * @param axis the axis (<code>null</code> permitted). 623 * 624 * @see #getDomainAxis() 625 */ 626 public void setDomainAxis(CategoryAxis axis) { 627 setDomainAxis(0, axis); 628 } 629 630 /** 631 * Sets a domain axis and sends a {@link PlotChangeEvent} to all 632 * registered listeners. 633 * 634 * @param index the axis index. 635 * @param axis the axis (<code>null</code> permitted). 636 * 637 * @see #getDomainAxis(int) 638 */ 639 public void setDomainAxis(int index, CategoryAxis axis) { 640 setDomainAxis(index, axis, true); 641 } 642 643 /** 644 * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to 645 * all registered listeners. 646 * 647 * @param index the axis index. 648 * @param axis the axis (<code>null</code> permitted). 649 * @param notify notify listeners? 650 */ 651 public void setDomainAxis(int index, CategoryAxis axis, boolean notify) { 652 CategoryAxis existing = (CategoryAxis) this.domainAxes.get(index); 653 if (existing != null) { 654 existing.removeChangeListener(this); 655 } 656 if (axis != null) { 657 axis.setPlot(this); 658 } 659 this.domainAxes.set(index, axis); 660 if (axis != null) { 661 axis.configure(); 662 axis.addChangeListener(this); 663 } 664 if (notify) { 665 notifyListeners(new PlotChangeEvent(this)); 666 } 667 } 668 669 /** 670 * Sets the domain axes for this plot and sends a {@link PlotChangeEvent} 671 * to all registered listeners. 672 * 673 * @param axes the axes (<code>null</code> not permitted). 674 * 675 * @see #setRangeAxes(ValueAxis[]) 676 */ 677 public void setDomainAxes(CategoryAxis[] axes) { 678 for (int i = 0; i < axes.length; i++) { 679 setDomainAxis(i, axes[i], false); 680 } 681 notifyListeners(new PlotChangeEvent(this)); 682 } 683 684 /** 685 * Returns the index of the specified axis, or <code>-1</code> if the axis 686 * is not assigned to the plot. 687 * 688 * @param axis the axis. 689 * 690 * @return The axis index. 691 * 692 * @since 1.0.3 693 */ 694 public int getDomainAxisIndex(CategoryAxis axis) { 695 return this.domainAxes.indexOf(axis); 696 } 697 698 /** 699 * Returns the domain axis location for the primary domain axis. 700 * 701 * @return The location (never <code>null</code>). 702 * 703 * @see #getRangeAxisLocation() 704 */ 705 public AxisLocation getDomainAxisLocation() { 706 return getDomainAxisLocation(0); 707 } 708 709 /** 710 * Returns the location for a domain axis. 711 * 712 * @param index the axis index. 713 * 714 * @return The location. 715 * 716 * @see #setDomainAxisLocation(int, AxisLocation) 717 */ 718 public AxisLocation getDomainAxisLocation(int index) { 719 AxisLocation result = null; 720 if (index < this.domainAxisLocations.size()) { 721 result = (AxisLocation) this.domainAxisLocations.get(index); 722 } 723 if (result == null) { 724 result = AxisLocation.getOpposite(getDomainAxisLocation(0)); 725 } 726 return result; 727 } 728 729 /** 730 * Sets the location of the domain axis and sends a {@link PlotChangeEvent} 731 * to all registered listeners. 732 * 733 * @param location the axis location (<code>null</code> not permitted). 734 * 735 * @see #getDomainAxisLocation() 736 * @see #setDomainAxisLocation(int, AxisLocation) 737 */ 738 public void setDomainAxisLocation(AxisLocation location) { 739 // delegate... 740 setDomainAxisLocation(0, location, true); 741 } 742 743 /** 744 * Sets the location of the domain axis and, if requested, sends a 745 * {@link PlotChangeEvent} to all registered listeners. 746 * 747 * @param location the axis location (<code>null</code> not permitted). 748 * @param notify a flag that controls whether listeners are notified. 749 */ 750 public void setDomainAxisLocation(AxisLocation location, boolean notify) { 751 // delegate... 752 setDomainAxisLocation(0, location, notify); 753 } 754 755 /** 756 * Sets the location for a domain axis and sends a {@link PlotChangeEvent} 757 * to all registered listeners. 758 * 759 * @param index the axis index. 760 * @param location the location. 761 * 762 * @see #getDomainAxisLocation(int) 763 * @see #setRangeAxisLocation(int, AxisLocation) 764 */ 765 public void setDomainAxisLocation(int index, AxisLocation location) { 766 // delegate... 767 setDomainAxisLocation(index, location, true); 768 } 769 770 /** 771 * Sets the location for a domain axis and sends a {@link PlotChangeEvent} 772 * to all registered listeners. 773 * 774 * @param index the axis index. 775 * @param location the location. 776 * @param notify notify listeners? 777 * 778 * @since 1.0.5 779 * 780 * @see #getDomainAxisLocation(int) 781 * @see #setRangeAxisLocation(int, AxisLocation, boolean) 782 */ 783 public void setDomainAxisLocation(int index, AxisLocation location, 784 boolean notify) { 785 if (index == 0 && location == null) { 786 throw new IllegalArgumentException( 787 "Null 'location' for index 0 not permitted."); 788 } 789 this.domainAxisLocations.set(index, location); 790 if (notify) { 791 notifyListeners(new PlotChangeEvent(this)); 792 } 793 } 794 795 /** 796 * Returns the domain axis edge. This is derived from the axis location 797 * and the plot orientation. 798 * 799 * @return The edge (never <code>null</code>). 800 */ 801 public RectangleEdge getDomainAxisEdge() { 802 return getDomainAxisEdge(0); 803 } 804 805 /** 806 * Returns the edge for a domain axis. 807 * 808 * @param index the axis index. 809 * 810 * @return The edge (never <code>null</code>). 811 */ 812 public RectangleEdge getDomainAxisEdge(int index) { 813 RectangleEdge result = null; 814 AxisLocation location = getDomainAxisLocation(index); 815 if (location != null) { 816 result = Plot.resolveDomainAxisLocation(location, this.orientation); 817 } 818 else { 819 result = RectangleEdge.opposite(getDomainAxisEdge(0)); 820 } 821 return result; 822 } 823 824 /** 825 * Returns the number of domain axes. 826 * 827 * @return The axis count. 828 */ 829 public int getDomainAxisCount() { 830 return this.domainAxes.size(); 831 } 832 833 /** 834 * Clears the domain axes from the plot and sends a {@link PlotChangeEvent} 835 * to all registered listeners. 836 */ 837 public void clearDomainAxes() { 838 for (int i = 0; i < this.domainAxes.size(); i++) { 839 CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i); 840 if (axis != null) { 841 axis.removeChangeListener(this); 842 } 843 } 844 this.domainAxes.clear(); 845 notifyListeners(new PlotChangeEvent(this)); 846 } 847 848 /** 849 * Configures the domain axes. 850 */ 851 public void configureDomainAxes() { 852 for (int i = 0; i < this.domainAxes.size(); i++) { 853 CategoryAxis axis = (CategoryAxis) this.domainAxes.get(i); 854 if (axis != null) { 855 axis.configure(); 856 } 857 } 858 } 859 860 /** 861 * Returns the range axis for the plot. If the range axis for this plot is 862 * null, then the method will return the parent plot's range axis (if there 863 * is a parent plot). 864 * 865 * @return The range axis (possibly <code>null</code>). 866 */ 867 public ValueAxis getRangeAxis() { 868 return getRangeAxis(0); 869 } 870 871 /** 872 * Returns a range axis. 873 * 874 * @param index the axis index. 875 * 876 * @return The axis (<code>null</code> possible). 877 */ 878 public ValueAxis getRangeAxis(int index) { 879 ValueAxis result = null; 880 if (index < this.rangeAxes.size()) { 881 result = (ValueAxis) this.rangeAxes.get(index); 882 } 883 if (result == null) { 884 Plot parent = getParent(); 885 if (parent instanceof CategoryPlot) { 886 CategoryPlot cp = (CategoryPlot) parent; 887 result = cp.getRangeAxis(index); 888 } 889 } 890 return result; 891 } 892 893 /** 894 * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to 895 * all registered listeners. 896 * 897 * @param axis the axis (<code>null</code> permitted). 898 */ 899 public void setRangeAxis(ValueAxis axis) { 900 setRangeAxis(0, axis); 901 } 902 903 /** 904 * Sets a range axis and sends a {@link PlotChangeEvent} to all registered 905 * listeners. 906 * 907 * @param index the axis index. 908 * @param axis the axis. 909 */ 910 public void setRangeAxis(int index, ValueAxis axis) { 911 setRangeAxis(index, axis, true); 912 } 913 914 /** 915 * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to 916 * all registered listeners. 917 * 918 * @param index the axis index. 919 * @param axis the axis. 920 * @param notify notify listeners? 921 */ 922 public void setRangeAxis(int index, ValueAxis axis, boolean notify) { 923 ValueAxis existing = (ValueAxis) this.rangeAxes.get(index); 924 if (existing != null) { 925 existing.removeChangeListener(this); 926 } 927 if (axis != null) { 928 axis.setPlot(this); 929 } 930 this.rangeAxes.set(index, axis); 931 if (axis != null) { 932 axis.configure(); 933 axis.addChangeListener(this); 934 } 935 if (notify) { 936 notifyListeners(new PlotChangeEvent(this)); 937 } 938 } 939 940 /** 941 * Sets the range axes for this plot and sends a {@link PlotChangeEvent} 942 * to all registered listeners. 943 * 944 * @param axes the axes (<code>null</code> not permitted). 945 * 946 * @see #setDomainAxes(CategoryAxis[]) 947 */ 948 public void setRangeAxes(ValueAxis[] axes) { 949 for (int i = 0; i < axes.length; i++) { 950 setRangeAxis(i, axes[i], false); 951 } 952 notifyListeners(new PlotChangeEvent(this)); 953 } 954 955 /** 956 * Returns the range axis location. 957 * 958 * @return The location (never <code>null</code>). 959 */ 960 public AxisLocation getRangeAxisLocation() { 961 return getRangeAxisLocation(0); 962 } 963 964 /** 965 * Returns the location for a range axis. 966 * 967 * @param index the axis index. 968 * 969 * @return The location. 970 * 971 * @see #setRangeAxisLocation(int, AxisLocation) 972 */ 973 public AxisLocation getRangeAxisLocation(int index) { 974 AxisLocation result = null; 975 if (index < this.rangeAxisLocations.size()) { 976 result = (AxisLocation) this.rangeAxisLocations.get(index); 977 } 978 if (result == null) { 979 result = AxisLocation.getOpposite(getRangeAxisLocation(0)); 980 } 981 return result; 982 } 983 984 /** 985 * Sets the location of the range axis and sends a {@link PlotChangeEvent} 986 * to all registered listeners. 987 * 988 * @param location the location (<code>null</code> not permitted). 989 * 990 * @see #setRangeAxisLocation(AxisLocation, boolean) 991 * @see #setDomainAxisLocation(AxisLocation) 992 */ 993 public void setRangeAxisLocation(AxisLocation location) { 994 // defer argument checking... 995 setRangeAxisLocation(location, true); 996 } 997 998 /** 999 * Sets the location of the range axis and, if requested, sends a 1000 * {@link PlotChangeEvent} to all registered listeners. 1001 * 1002 * @param location the location (<code>null</code> not permitted). 1003 * @param notify notify listeners? 1004 * 1005 * @see #setDomainAxisLocation(AxisLocation, boolean) 1006 */ 1007 public void setRangeAxisLocation(AxisLocation location, boolean notify) { 1008 setRangeAxisLocation(0, location, notify); 1009 } 1010 1011 /** 1012 * Sets the location for a range axis and sends a {@link PlotChangeEvent} 1013 * to all registered listeners. 1014 * 1015 * @param index the axis index. 1016 * @param location the location. 1017 * 1018 * @see #getRangeAxisLocation(int) 1019 * @see #setRangeAxisLocation(int, AxisLocation, boolean) 1020 */ 1021 public void setRangeAxisLocation(int index, AxisLocation location) { 1022 setRangeAxisLocation(index, location, true); 1023 } 1024 1025 /** 1026 * Sets the location for a range axis and sends a {@link PlotChangeEvent} 1027 * to all registered listeners. 1028 * 1029 * @param index the axis index. 1030 * @param location the location. 1031 * @param notify notify listeners? 1032 * 1033 * @see #getRangeAxisLocation(int) 1034 * @see #setDomainAxisLocation(int, AxisLocation, boolean) 1035 */ 1036 public void setRangeAxisLocation(int index, AxisLocation location, 1037 boolean notify) { 1038 if (index == 0 && location == null) { 1039 throw new IllegalArgumentException( 1040 "Null 'location' for index 0 not permitted."); 1041 } 1042 this.rangeAxisLocations.set(index, location); 1043 if (notify) { 1044 notifyListeners(new PlotChangeEvent(this)); 1045 } 1046 } 1047 1048 /** 1049 * Returns the edge where the primary range axis is located. 1050 * 1051 * @return The edge (never <code>null</code>). 1052 */ 1053 public RectangleEdge getRangeAxisEdge() { 1054 return getRangeAxisEdge(0); 1055 } 1056 1057 /** 1058 * Returns the edge for a range axis. 1059 * 1060 * @param index the axis index. 1061 * 1062 * @return The edge. 1063 */ 1064 public RectangleEdge getRangeAxisEdge(int index) { 1065 AxisLocation location = getRangeAxisLocation(index); 1066 RectangleEdge result = Plot.resolveRangeAxisLocation(location, 1067 this.orientation); 1068 if (result == null) { 1069 result = RectangleEdge.opposite(getRangeAxisEdge(0)); 1070 } 1071 return result; 1072 } 1073 1074 /** 1075 * Returns the number of range axes. 1076 * 1077 * @return The axis count. 1078 */ 1079 public int getRangeAxisCount() { 1080 return this.rangeAxes.size(); 1081 } 1082 1083 /** 1084 * Clears the range axes from the plot and sends a {@link PlotChangeEvent} 1085 * to all registered listeners. 1086 */ 1087 public void clearRangeAxes() { 1088 for (int i = 0; i < this.rangeAxes.size(); i++) { 1089 ValueAxis axis = (ValueAxis) this.rangeAxes.get(i); 1090 if (axis != null) { 1091 axis.removeChangeListener(this); 1092 } 1093 } 1094 this.rangeAxes.clear(); 1095 notifyListeners(new PlotChangeEvent(this)); 1096 } 1097 1098 /** 1099 * Configures the range axes. 1100 */ 1101 public void configureRangeAxes() { 1102 for (int i = 0; i < this.rangeAxes.size(); i++) { 1103 ValueAxis axis = (ValueAxis) this.rangeAxes.get(i); 1104 if (axis != null) { 1105 axis.configure(); 1106 } 1107 } 1108 } 1109 1110 /** 1111 * Returns the primary dataset for the plot. 1112 * 1113 * @return The primary dataset (possibly <code>null</code>). 1114 * 1115 * @see #setDataset(CategoryDataset) 1116 */ 1117 public CategoryDataset getDataset() { 1118 return getDataset(0); 1119 } 1120 1121 /** 1122 * Returns the dataset at the given index. 1123 * 1124 * @param index the dataset index. 1125 * 1126 * @return The dataset (possibly <code>null</code>). 1127 * 1128 * @see #setDataset(int, CategoryDataset) 1129 */ 1130 public CategoryDataset getDataset(int index) { 1131 CategoryDataset result = null; 1132 if (this.datasets.size() > index) { 1133 result = (CategoryDataset) this.datasets.get(index); 1134 } 1135 return result; 1136 } 1137 1138 /** 1139 * Sets the dataset for the plot, replacing the existing dataset, if there 1140 * is one. This method also calls the 1141 * {@link #datasetChanged(DatasetChangeEvent)} method, which adjusts the 1142 * axis ranges if necessary and sends a {@link PlotChangeEvent} to all 1143 * registered listeners. 1144 * 1145 * @param dataset the dataset (<code>null</code> permitted). 1146 * 1147 * @see #getDataset() 1148 */ 1149 public void setDataset(CategoryDataset dataset) { 1150 setDataset(0, dataset); 1151 } 1152 1153 /** 1154 * Sets a dataset for the plot. 1155 * 1156 * @param index the dataset index. 1157 * @param dataset the dataset (<code>null</code> permitted). 1158 * 1159 * @see #getDataset(int) 1160 */ 1161 public void setDataset(int index, CategoryDataset dataset) { 1162 1163 CategoryDataset existing = (CategoryDataset) this.datasets.get(index); 1164 if (existing != null) { 1165 existing.removeChangeListener(this); 1166 } 1167 this.datasets.set(index, dataset); 1168 if (dataset != null) { 1169 dataset.addChangeListener(this); 1170 } 1171 1172 // send a dataset change event to self... 1173 DatasetChangeEvent event = new DatasetChangeEvent(this, dataset); 1174 datasetChanged(event); 1175 1176 } 1177 1178 /** 1179 * Returns the number of datasets. 1180 * 1181 * @return The number of datasets. 1182 * 1183 * @since 1.0.2 1184 */ 1185 public int getDatasetCount() { 1186 return this.datasets.size(); 1187 } 1188 1189 /** 1190 * Maps a dataset to a particular domain axis. 1191 * 1192 * @param index the dataset index (zero-based). 1193 * @param axisIndex the axis index (zero-based). 1194 * 1195 * @see #getDomainAxisForDataset(int) 1196 */ 1197 public void mapDatasetToDomainAxis(int index, int axisIndex) { 1198 this.datasetToDomainAxisMap.set(index, new Integer(axisIndex)); 1199 // fake a dataset change event to update axes... 1200 datasetChanged(new DatasetChangeEvent(this, getDataset(index))); 1201 } 1202 1203 /** 1204 * Returns the domain axis for a dataset. You can change the axis for a 1205 * dataset using the {@link #mapDatasetToDomainAxis(int, int)} method. 1206 * 1207 * @param index the dataset index. 1208 * 1209 * @return The domain axis. 1210 * 1211 * @see #mapDatasetToDomainAxis(int, int) 1212 */ 1213 public CategoryAxis getDomainAxisForDataset(int index) { 1214 CategoryAxis result = getDomainAxis(); 1215 Integer axisIndex = (Integer) this.datasetToDomainAxisMap.get(index); 1216 if (axisIndex != null) { 1217 result = getDomainAxis(axisIndex.intValue()); 1218 } 1219 return result; 1220 } 1221 1222 /** 1223 * Maps a dataset to a particular range axis. 1224 * 1225 * @param index the dataset index (zero-based). 1226 * @param axisIndex the axis index (zero-based). 1227 * 1228 * @see #getRangeAxisForDataset(int) 1229 */ 1230 public void mapDatasetToRangeAxis(int index, int axisIndex) { 1231 this.datasetToRangeAxisMap.set(index, new Integer(axisIndex)); 1232 // fake a dataset change event to update axes... 1233 datasetChanged(new DatasetChangeEvent(this, getDataset(index))); 1234 } 1235 1236 /** 1237 * Returns the range axis for a dataset. You can change the axis for a 1238 * dataset using the {@link #mapDatasetToRangeAxis(int, int)} method. 1239 * 1240 * @param index the dataset index. 1241 * 1242 * @return The range axis. 1243 * 1244 * @see #mapDatasetToRangeAxis(int, int) 1245 */ 1246 public ValueAxis getRangeAxisForDataset(int index) { 1247 ValueAxis result = getRangeAxis(); 1248 Integer axisIndex = (Integer) this.datasetToRangeAxisMap.get(index); 1249 if (axisIndex != null) { 1250 result = getRangeAxis(axisIndex.intValue()); 1251 } 1252 return result; 1253 } 1254 1255 /** 1256 * Returns a reference to the renderer for the plot. 1257 * 1258 * @return The renderer. 1259 * 1260 * @see #setRenderer(CategoryItemRenderer) 1261 */ 1262 public CategoryItemRenderer getRenderer() { 1263 return getRenderer(0); 1264 } 1265 1266 /** 1267 * Returns the renderer at the given index. 1268 * 1269 * @param index the renderer index. 1270 * 1271 * @return The renderer (possibly <code>null</code>). 1272 * 1273 * @see #setRenderer(int, CategoryItemRenderer) 1274 */ 1275 public CategoryItemRenderer getRenderer(int index) { 1276 CategoryItemRenderer result = null; 1277 if (this.renderers.size() > index) { 1278 result = (CategoryItemRenderer) this.renderers.get(index); 1279 } 1280 return result; 1281 } 1282 1283 /** 1284 * Sets the renderer at index 0 (sometimes referred to as the "primary" 1285 * renderer) and sends a {@link PlotChangeEvent} to all registered 1286 * listeners. 1287 * 1288 * @param renderer the renderer (<code>null</code> permitted. 1289 * 1290 * @see #getRenderer() 1291 */ 1292 public void setRenderer(CategoryItemRenderer renderer) { 1293 setRenderer(0, renderer, true); 1294 } 1295 1296 /** 1297 * Sets the renderer at index 0 (sometimes referred to as the "primary" 1298 * renderer) and, if requested, sends a {@link PlotChangeEvent} to all 1299 * registered listeners. 1300 * <p> 1301 * You can set the renderer to <code>null</code>, but this is not 1302 * recommended because: 1303 * <ul> 1304 * <li>no data will be displayed;</li> 1305 * <li>the plot background will not be painted;</li> 1306 * </ul> 1307 * 1308 * @param renderer the renderer (<code>null</code> permitted). 1309 * @param notify notify listeners? 1310 * 1311 * @see #getRenderer() 1312 */ 1313 public void setRenderer(CategoryItemRenderer renderer, boolean notify) { 1314 setRenderer(0, renderer, notify); 1315 } 1316 1317 /** 1318 * Sets the renderer at the specified index and sends a 1319 * {@link PlotChangeEvent} to all registered listeners. 1320 * 1321 * @param index the index. 1322 * @param renderer the renderer (<code>null</code> permitted). 1323 * 1324 * @see #getRenderer(int) 1325 * @see #setRenderer(int, CategoryItemRenderer, boolean) 1326 */ 1327 public void setRenderer(int index, CategoryItemRenderer renderer) { 1328 setRenderer(index, renderer, true); 1329 } 1330 1331 /** 1332 * Sets a renderer. A {@link PlotChangeEvent} is sent to all registered 1333 * listeners. 1334 * 1335 * @param index the index. 1336 * @param renderer the renderer (<code>null</code> permitted). 1337 * @param notify notify listeners? 1338 * 1339 * @see #getRenderer(int) 1340 */ 1341 public void setRenderer(int index, CategoryItemRenderer renderer, 1342 boolean notify) { 1343 1344 // stop listening to the existing renderer... 1345 CategoryItemRenderer existing 1346 = (CategoryItemRenderer) this.renderers.get(index); 1347 if (existing != null) { 1348 existing.removeChangeListener(this); 1349 } 1350 1351 // register the new renderer... 1352 this.renderers.set(index, renderer); 1353 if (renderer != null) { 1354 renderer.setPlot(this); 1355 renderer.addChangeListener(this); 1356 } 1357 1358 configureDomainAxes(); 1359 configureRangeAxes(); 1360 1361 if (notify) { 1362 notifyListeners(new PlotChangeEvent(this)); 1363 } 1364 } 1365 1366 /** 1367 * Sets the renderers for this plot and sends a {@link PlotChangeEvent} 1368 * to all registered listeners. 1369 * 1370 * @param renderers the renderers. 1371 */ 1372 public void setRenderers(CategoryItemRenderer[] renderers) { 1373 for (int i = 0; i < renderers.length; i++) { 1374 setRenderer(i, renderers[i], false); 1375 } 1376 notifyListeners(new PlotChangeEvent(this)); 1377 } 1378 1379 /** 1380 * Returns the renderer for the specified dataset. If the dataset doesn't 1381 * belong to the plot, this method will return <code>null</code>. 1382 * 1383 * @param dataset the dataset (<code>null</code> permitted). 1384 * 1385 * @return The renderer (possibly <code>null</code>). 1386 */ 1387 public CategoryItemRenderer getRendererForDataset(CategoryDataset dataset) { 1388 CategoryItemRenderer result = null; 1389 for (int i = 0; i < this.datasets.size(); i++) { 1390 if (this.datasets.get(i) == dataset) { 1391 result = (CategoryItemRenderer) this.renderers.get(i); 1392 break; 1393 } 1394 } 1395 return result; 1396 } 1397 1398 /** 1399 * Returns the index of the specified renderer, or <code>-1</code> if the 1400 * renderer is not assigned to this plot. 1401 * 1402 * @param renderer the renderer (<code>null</code> permitted). 1403 * 1404 * @return The renderer index. 1405 */ 1406 public int getIndexOf(CategoryItemRenderer renderer) { 1407 return this.renderers.indexOf(renderer); 1408 } 1409 1410 /** 1411 * Returns the dataset rendering order. 1412 * 1413 * @return The order (never <code>null</code>). 1414 * 1415 * @see #setDatasetRenderingOrder(DatasetRenderingOrder) 1416 */ 1417 public DatasetRenderingOrder getDatasetRenderingOrder() { 1418 return this.renderingOrder; 1419 } 1420 1421 /** 1422 * Sets the rendering order and sends a {@link PlotChangeEvent} to all 1423 * registered listeners. By default, the plot renders the primary dataset 1424 * last (so that the primary dataset overlays the secondary datasets). You 1425 * can reverse this if you want to. 1426 * 1427 * @param order the rendering order (<code>null</code> not permitted). 1428 * 1429 * @see #getDatasetRenderingOrder() 1430 */ 1431 public void setDatasetRenderingOrder(DatasetRenderingOrder order) { 1432 if (order == null) { 1433 throw new IllegalArgumentException("Null 'order' argument."); 1434 } 1435 this.renderingOrder = order; 1436 notifyListeners(new PlotChangeEvent(this)); 1437 } 1438 1439 /** 1440 * Returns the order in which the columns are rendered. The default value 1441 * is <code>SortOrder.ASCENDING</code>. 1442 * 1443 * @return The column rendering order (never <code>null</code). 1444 * 1445 * @see #setColumnRenderingOrder(SortOrder) 1446 */ 1447 public SortOrder getColumnRenderingOrder() { 1448 return this.columnRenderingOrder; 1449 } 1450 1451 /** 1452 * Sets the column order in which the items in each dataset should be 1453 * rendered and sends a {@link PlotChangeEvent} to all registered 1454 * listeners. Note that this affects the order in which items are drawn, 1455 * NOT their position in the chart. 1456 * 1457 * @param order the order (<code>null</code> not permitted). 1458 * 1459 * @see #getColumnRenderingOrder() 1460 * @see #setRowRenderingOrder(SortOrder) 1461 */ 1462 public void setColumnRenderingOrder(SortOrder order) { 1463 if (order == null) { 1464 throw new IllegalArgumentException("Null 'order' argument."); 1465 } 1466 this.columnRenderingOrder = order; 1467 notifyListeners(new PlotChangeEvent(this)); 1468 } 1469 1470 /** 1471 * Returns the order in which the rows should be rendered. The default 1472 * value is <code>SortOrder.ASCENDING</code>. 1473 * 1474 * @return The order (never <code>null</code>). 1475 * 1476 * @see #setRowRenderingOrder(SortOrder) 1477 */ 1478 public SortOrder getRowRenderingOrder() { 1479 return this.rowRenderingOrder; 1480 } 1481 1482 /** 1483 * Sets the row order in which the items in each dataset should be 1484 * rendered and sends a {@link PlotChangeEvent} to all registered 1485 * listeners. Note that this affects the order in which items are drawn, 1486 * NOT their position in the chart. 1487 * 1488 * @param order the order (<code>null</code> not permitted). 1489 * 1490 * @see #getRowRenderingOrder() 1491 * @see #setColumnRenderingOrder(SortOrder) 1492 */ 1493 public void setRowRenderingOrder(SortOrder order) { 1494 if (order == null) { 1495 throw new IllegalArgumentException("Null 'order' argument."); 1496 } 1497 this.rowRenderingOrder = order; 1498 notifyListeners(new PlotChangeEvent(this)); 1499 } 1500 1501 /** 1502 * Returns the flag that controls whether the domain grid-lines are visible. 1503 * 1504 * @return The <code>true</code> or <code>false</code>. 1505 * 1506 * @see #setDomainGridlinesVisible(boolean) 1507 */ 1508 public boolean isDomainGridlinesVisible() { 1509 return this.domainGridlinesVisible; 1510 } 1511 1512 /** 1513 * Sets the flag that controls whether or not grid-lines are drawn against 1514 * the domain axis. 1515 * <p> 1516 * If the flag value changes, a {@link PlotChangeEvent} is sent to all 1517 * registered listeners. 1518 * 1519 * @param visible the new value of the flag. 1520 * 1521 * @see #isDomainGridlinesVisible() 1522 */ 1523 public void setDomainGridlinesVisible(boolean visible) { 1524 if (this.domainGridlinesVisible != visible) { 1525 this.domainGridlinesVisible = visible; 1526 notifyListeners(new PlotChangeEvent(this)); 1527 } 1528 } 1529 1530 /** 1531 * Returns the position used for the domain gridlines. 1532 * 1533 * @return The gridline position (never <code>null</code>). 1534 * 1535 * @see #setDomainGridlinePosition(CategoryAnchor) 1536 */ 1537 public CategoryAnchor getDomainGridlinePosition() { 1538 return this.domainGridlinePosition; 1539 } 1540 1541 /** 1542 * Sets the position used for the domain gridlines and sends a 1543 * {@link PlotChangeEvent} to all registered listeners. 1544 * 1545 * @param position the position (<code>null</code> not permitted). 1546 * 1547 * @see #getDomainGridlinePosition() 1548 */ 1549 public void setDomainGridlinePosition(CategoryAnchor position) { 1550 if (position == null) { 1551 throw new IllegalArgumentException("Null 'position' argument."); 1552 } 1553 this.domainGridlinePosition = position; 1554 notifyListeners(new PlotChangeEvent(this)); 1555 } 1556 1557 /** 1558 * Returns the stroke used to draw grid-lines against the domain axis. 1559 * 1560 * @return The stroke (never <code>null</code>). 1561 * 1562 * @see #setDomainGridlineStroke(Stroke) 1563 */ 1564 public Stroke getDomainGridlineStroke() { 1565 return this.domainGridlineStroke; 1566 } 1567 1568 /** 1569 * Sets the stroke used to draw grid-lines against the domain axis and 1570 * sends a {@link PlotChangeEvent} to all registered listeners. 1571 * 1572 * @param stroke the stroke (<code>null</code> not permitted). 1573 * 1574 * @see #getDomainGridlineStroke() 1575 */ 1576 public void setDomainGridlineStroke(Stroke stroke) { 1577 if (stroke == null) { 1578 throw new IllegalArgumentException("Null 'stroke' not permitted."); 1579 } 1580 this.domainGridlineStroke = stroke; 1581 notifyListeners(new PlotChangeEvent(this)); 1582 } 1583 1584 /** 1585 * Returns the paint used to draw grid-lines against the domain axis. 1586 * 1587 * @return The paint (never <code>null</code>). 1588 * 1589 * @see #setDomainGridlinePaint(Paint) 1590 */ 1591 public Paint getDomainGridlinePaint() { 1592 return this.domainGridlinePaint; 1593 } 1594 1595 /** 1596 * Sets the paint used to draw the grid-lines (if any) against the domain 1597 * axis and sends a {@link PlotChangeEvent} to all registered listeners. 1598 * 1599 * @param paint the paint (<code>null</code> not permitted). 1600 * 1601 * @see #getDomainGridlinePaint() 1602 */ 1603 public void setDomainGridlinePaint(Paint paint) { 1604 if (paint == null) { 1605 throw new IllegalArgumentException("Null 'paint' argument."); 1606 } 1607 this.domainGridlinePaint = paint; 1608 notifyListeners(new PlotChangeEvent(this)); 1609 } 1610 1611 /** 1612 * Returns the flag that controls whether the range grid-lines are visible. 1613 * 1614 * @return The flag. 1615 * 1616 * @see #setRangeGridlinesVisible(boolean) 1617 */ 1618 public boolean isRangeGridlinesVisible() { 1619 return this.rangeGridlinesVisible; 1620 } 1621 1622 /** 1623 * Sets the flag that controls whether or not grid-lines are drawn against 1624 * the range axis. If the flag changes value, a {@link PlotChangeEvent} is 1625 * sent to all registered listeners. 1626 * 1627 * @param visible the new value of the flag. 1628 * 1629 * @see #isRangeGridlinesVisible() 1630 */ 1631 public void setRangeGridlinesVisible(boolean visible) { 1632 if (this.rangeGridlinesVisible != visible) { 1633 this.rangeGridlinesVisible = visible; 1634 notifyListeners(new PlotChangeEvent(this)); 1635 } 1636 } 1637 1638 /** 1639 * Returns the stroke used to draw the grid-lines against the range axis. 1640 * 1641 * @return The stroke (never <code>null</code>). 1642 * 1643 * @see #setRangeGridlineStroke(Stroke) 1644 */ 1645 public Stroke getRangeGridlineStroke() { 1646 return this.rangeGridlineStroke; 1647 } 1648 1649 /** 1650 * Sets the stroke used to draw the grid-lines against the range axis and 1651 * sends a {@link PlotChangeEvent} to all registered listeners. 1652 * 1653 * @param stroke the stroke (<code>null</code> not permitted). 1654 * 1655 * @see #getRangeGridlineStroke() 1656 */ 1657 public void setRangeGridlineStroke(Stroke stroke) { 1658 if (stroke == null) { 1659 throw new IllegalArgumentException("Null 'stroke' argument."); 1660 } 1661 this.rangeGridlineStroke = stroke; 1662 notifyListeners(new PlotChangeEvent(this)); 1663 } 1664 1665 /** 1666 * Returns the paint used to draw the grid-lines against the range axis. 1667 * 1668 * @return The paint (never <code>null</code>). 1669 * 1670 * @see #setRangeGridlinePaint(Paint) 1671 */ 1672 public Paint getRangeGridlinePaint() { 1673 return this.rangeGridlinePaint; 1674 } 1675 1676 /** 1677 * Sets the paint used to draw the grid lines against the range axis and 1678 * sends a {@link PlotChangeEvent} to all registered listeners. 1679 * 1680 * @param paint the paint (<code>null</code> not permitted). 1681 * 1682 * @see #getRangeGridlinePaint() 1683 */ 1684 public void setRangeGridlinePaint(Paint paint) { 1685 if (paint == null) { 1686 throw new IllegalArgumentException("Null 'paint' argument."); 1687 } 1688 this.rangeGridlinePaint = paint; 1689 notifyListeners(new PlotChangeEvent(this)); 1690 } 1691 1692 /** 1693 * Returns the fixed legend items, if any. 1694 * 1695 * @return The legend items (possibly <code>null</code>). 1696 * 1697 * @see #setFixedLegendItems(LegendItemCollection) 1698 */ 1699 public LegendItemCollection getFixedLegendItems() { 1700 return this.fixedLegendItems; 1701 } 1702 1703 /** 1704 * Sets the fixed legend items for the plot. Leave this set to 1705 * <code>null</code> if you prefer the legend items to be created 1706 * automatically. 1707 * 1708 * @param items the legend items (<code>null</code> permitted). 1709 * 1710 * @see #getFixedLegendItems() 1711 */ 1712 public void setFixedLegendItems(LegendItemCollection items) { 1713 this.fixedLegendItems = items; 1714 notifyListeners(new PlotChangeEvent(this)); 1715 } 1716 1717 /** 1718 * Returns the legend items for the plot. By default, this method creates 1719 * a legend item for each series in each of the datasets. You can change 1720 * this behaviour by overriding this method. 1721 * 1722 * @return The legend items. 1723 */ 1724 public LegendItemCollection getLegendItems() { 1725 LegendItemCollection result = this.fixedLegendItems; 1726 if (result == null) { 1727 result = new LegendItemCollection(); 1728 // get the legend items for the datasets... 1729 int count = this.datasets.size(); 1730 for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) { 1731 CategoryDataset dataset = getDataset(datasetIndex); 1732 if (dataset != null) { 1733 CategoryItemRenderer renderer = getRenderer(datasetIndex); 1734 if (renderer != null) { 1735 int seriesCount = dataset.getRowCount(); 1736 for (int i = 0; i < seriesCount; i++) { 1737 LegendItem item = renderer.getLegendItem( 1738 datasetIndex, i); 1739 if (item != null) { 1740 result.add(item); 1741 } 1742 } 1743 } 1744 } 1745 } 1746 } 1747 return result; 1748 } 1749 1750 /** 1751 * Handles a 'click' on the plot by updating the anchor value. 1752 * 1753 * @param x x-coordinate of the click (in Java2D space). 1754 * @param y y-coordinate of the click (in Java2D space). 1755 * @param info information about the plot's dimensions. 1756 * 1757 */ 1758 public void handleClick(int x, int y, PlotRenderingInfo info) { 1759 1760 Rectangle2D dataArea = info.getDataArea(); 1761 if (dataArea.contains(x, y)) { 1762 // set the anchor value for the range axis... 1763 double java2D = 0.0; 1764 if (this.orientation == PlotOrientation.HORIZONTAL) { 1765 java2D = x; 1766 } 1767 else if (this.orientation == PlotOrientation.VERTICAL) { 1768 java2D = y; 1769 } 1770 RectangleEdge edge = Plot.resolveRangeAxisLocation( 1771 getRangeAxisLocation(), this.orientation); 1772 double value = getRangeAxis().java2DToValue( 1773 java2D, info.getDataArea(), edge); 1774 setAnchorValue(value); 1775 setRangeCrosshairValue(value); 1776 } 1777 1778 } 1779 1780 /** 1781 * Zooms (in or out) on the plot's value axis. 1782 * <p> 1783 * If the value 0.0 is passed in as the zoom percent, the auto-range 1784 * calculation for the axis is restored (which sets the range to include 1785 * the minimum and maximum data values, thus displaying all the data). 1786 * 1787 * @param percent the zoom amount. 1788 */ 1789 public void zoom(double percent) { 1790 1791 if (percent > 0.0) { 1792 double range = getRangeAxis().getRange().getLength(); 1793 double scaledRange = range * percent; 1794 getRangeAxis().setRange(this.anchorValue - scaledRange / 2.0, 1795 this.anchorValue + scaledRange / 2.0); 1796 } 1797 else { 1798 getRangeAxis().setAutoRange(true); 1799 } 1800 1801 } 1802 1803 /** 1804 * Receives notification of a change to the plot's dataset. 1805 * <P> 1806 * The range axis bounds will be recalculated if necessary. 1807 * 1808 * @param event information about the event (not used here). 1809 */ 1810 public void datasetChanged(DatasetChangeEvent event) { 1811 1812 int count = this.rangeAxes.size(); 1813 for (int axisIndex = 0; axisIndex < count; axisIndex++) { 1814 ValueAxis yAxis = getRangeAxis(axisIndex); 1815 if (yAxis != null) { 1816 yAxis.configure(); 1817 } 1818 } 1819 if (getParent() != null) { 1820 getParent().datasetChanged(event); 1821 } 1822 else { 1823 PlotChangeEvent e = new PlotChangeEvent(this); 1824 e.setType(ChartChangeEventType.DATASET_UPDATED); 1825 notifyListeners(e); 1826 } 1827 1828 } 1829 1830 /** 1831 * Receives notification of a renderer change event. 1832 * 1833 * @param event the event. 1834 */ 1835 public void rendererChanged(RendererChangeEvent event) { 1836 Plot parent = getParent(); 1837 if (parent != null) { 1838 if (parent instanceof RendererChangeListener) { 1839 RendererChangeListener rcl = (RendererChangeListener) parent; 1840 rcl.rendererChanged(event); 1841 } 1842 else { 1843 // this should never happen with the existing code, but throw 1844 // an exception in case future changes make it possible... 1845 throw new RuntimeException( 1846 "The renderer has changed and I don't know what to do!"); 1847 } 1848 } 1849 else { 1850 configureRangeAxes(); 1851 PlotChangeEvent e = new PlotChangeEvent(this); 1852 notifyListeners(e); 1853 } 1854 } 1855 1856 /** 1857 * Adds a marker for display (in the foreground) against the domain axis and 1858 * sends a {@link PlotChangeEvent} to all registered listeners. Typically a 1859 * marker will be drawn by the renderer as a line perpendicular to the 1860 * domain axis, however this is entirely up to the renderer. 1861 * 1862 * @param marker the marker (<code>null</code> not permitted). 1863 */ 1864 public void addDomainMarker(CategoryMarker marker) { 1865 addDomainMarker(marker, Layer.FOREGROUND); 1866 } 1867 1868 /** 1869 * Adds a marker for display against the domain axis and sends a 1870 * {@link PlotChangeEvent} to all registered listeners. Typically a marker 1871 * will be drawn by the renderer as a line perpendicular to the domain axis, 1872 * however this is entirely up to the renderer. 1873 * 1874 * @param marker the marker (<code>null</code> not permitted). 1875 * @param layer the layer (foreground or background) (<code>null</code> 1876 * not permitted). 1877 */ 1878 public void addDomainMarker(CategoryMarker marker, Layer layer) { 1879 addDomainMarker(0, marker, layer); 1880 } 1881 1882 /** 1883 * Adds a marker for display by a particular renderer. 1884 * <P> 1885 * Typically a marker will be drawn by the renderer as a line perpendicular 1886 * to a domain axis, however this is entirely up to the renderer. 1887 * 1888 * @param index the renderer index. 1889 * @param marker the marker (<code>null</code> not permitted). 1890 * @param layer the layer (<code>null</code> not permitted). 1891 */ 1892 public void addDomainMarker(int index, CategoryMarker marker, Layer layer) { 1893 if (marker == null) { 1894 throw new IllegalArgumentException("Null 'marker' not permitted."); 1895 } 1896 if (layer == null) { 1897 throw new IllegalArgumentException("Null 'layer' not permitted."); 1898 } 1899 Collection markers; 1900 if (layer == Layer.FOREGROUND) { 1901 markers = (Collection) this.foregroundDomainMarkers.get( 1902 new Integer(index)); 1903 if (markers == null) { 1904 markers = new java.util.ArrayList(); 1905 this.foregroundDomainMarkers.put(new Integer(index), markers); 1906 } 1907 markers.add(marker); 1908 } 1909 else if (layer == Layer.BACKGROUND) { 1910 markers = (Collection) this.backgroundDomainMarkers.get( 1911 new Integer(index)); 1912 if (markers == null) { 1913 markers = new java.util.ArrayList(); 1914 this.backgroundDomainMarkers.put(new Integer(index), markers); 1915 } 1916 markers.add(marker); 1917 } 1918 marker.addChangeListener(this); 1919 notifyListeners(new PlotChangeEvent(this)); 1920 } 1921 1922 /** 1923 * Clears all the domain markers for the plot and sends a 1924 * {@link PlotChangeEvent} to all registered listeners. 1925 * 1926 * @see #clearRangeMarkers() 1927 */ 1928 public void clearDomainMarkers() { 1929 if (this.backgroundDomainMarkers != null) { 1930 Set keys = this.backgroundDomainMarkers.keySet(); 1931 Iterator iterator = keys.iterator(); 1932 while (iterator.hasNext()) { 1933 Integer key = (Integer) iterator.next(); 1934 clearDomainMarkers(key.intValue()); 1935 } 1936 this.backgroundDomainMarkers.clear(); 1937 } 1938 if (this.foregroundDomainMarkers != null) { 1939 Set keys = this.foregroundDomainMarkers.keySet(); 1940 Iterator iterator = keys.iterator(); 1941 while (iterator.hasNext()) { 1942 Integer key = (Integer) iterator.next(); 1943 clearDomainMarkers(key.intValue()); 1944 } 1945 this.foregroundDomainMarkers.clear(); 1946 } 1947 notifyListeners(new PlotChangeEvent(this)); 1948 } 1949 1950 /** 1951 * Returns the list of domain markers (read only) for the specified layer. 1952 * 1953 * @param layer the layer (foreground or background). 1954 * 1955 * @return The list of domain markers. 1956 */ 1957 public Collection getDomainMarkers(Layer layer) { 1958 return getDomainMarkers(0, layer); 1959 } 1960 1961 /** 1962 * Returns a collection of domain markers for a particular renderer and 1963 * layer. 1964 * 1965 * @param index the renderer index. 1966 * @param layer the layer. 1967 * 1968 * @return A collection of markers (possibly <code>null</code>). 1969 */ 1970 public Collection getDomainMarkers(int index, Layer layer) { 1971 Collection result = null; 1972 Integer key = new Integer(index); 1973 if (layer == Layer.FOREGROUND) { 1974 result = (Collection) this.foregroundDomainMarkers.get(key); 1975 } 1976 else if (layer == Layer.BACKGROUND) { 1977 result = (Collection) this.backgroundDomainMarkers.get(key); 1978 } 1979 if (result != null) { 1980 result = Collections.unmodifiableCollection(result); 1981 } 1982 return result; 1983 } 1984 1985 /** 1986 * Clears all the domain markers for the specified renderer. 1987 * 1988 * @param index the renderer index. 1989 * 1990 * @see #clearRangeMarkers(int) 1991 */ 1992 public void clearDomainMarkers(int index) { 1993 Integer key = new Integer(index); 1994 if (this.backgroundDomainMarkers != null) { 1995 Collection markers 1996 = (Collection) this.backgroundDomainMarkers.get(key); 1997 if (markers != null) { 1998 Iterator iterator = markers.iterator(); 1999 while (iterator.hasNext()) { 2000 Marker m = (Marker) iterator.next(); 2001 m.removeChangeListener(this); 2002 } 2003 markers.clear(); 2004 } 2005 } 2006 if (this.foregroundDomainMarkers != null) { 2007 Collection markers 2008 = (Collection) this.foregroundDomainMarkers.get(key); 2009 if (markers != null) { 2010 Iterator iterator = markers.iterator(); 2011 while (iterator.hasNext()) { 2012 Marker m = (Marker) iterator.next(); 2013 m.removeChangeListener(this); 2014 } 2015 markers.clear(); 2016 } 2017 } 2018 notifyListeners(new PlotChangeEvent(this)); 2019 } 2020 2021 /** 2022 * Adds a marker for display (in the foreground) against the range axis and 2023 * sends a {@link PlotChangeEvent} to all registered listeners. Typically a 2024 * marker will be drawn by the renderer as a line perpendicular to the 2025 * range axis, however this is entirely up to the renderer. 2026 * 2027 * @param marker the marker (<code>null</code> not permitted). 2028 */ 2029 public void addRangeMarker(Marker marker) { 2030 addRangeMarker(marker, Layer.FOREGROUND); 2031 } 2032 2033 /** 2034 * Adds a marker for display against the range axis and sends a 2035 * {@link PlotChangeEvent} to all registered listeners. Typically a marker 2036 * will be drawn by the renderer as a line perpendicular to the range axis, 2037 * however this is entirely up to the renderer. 2038 * 2039 * @param marker the marker (<code>null</code> not permitted). 2040 * @param layer the layer (foreground or background) (<code>null</code> 2041 * not permitted). 2042 */ 2043 public void addRangeMarker(Marker marker, Layer layer) { 2044 addRangeMarker(0, marker, layer); 2045 } 2046 2047 /** 2048 * Adds a marker for display by a particular renderer. 2049 * <P> 2050 * Typically a marker will be drawn by the renderer as a line perpendicular 2051 * to a range axis, however this is entirely up to the renderer. 2052 * 2053 * @param index the renderer index. 2054 * @param marker the marker. 2055 * @param layer the layer. 2056 */ 2057 public void addRangeMarker(int index, Marker marker, Layer layer) { 2058 Collection markers; 2059 if (layer == Layer.FOREGROUND) { 2060 markers = (Collection) this.foregroundRangeMarkers.get( 2061 new Integer(index)); 2062 if (markers == null) { 2063 markers = new java.util.ArrayList(); 2064 this.foregroundRangeMarkers.put(new Integer(index), markers); 2065 } 2066 markers.add(marker); 2067 } 2068 else if (layer == Layer.BACKGROUND) { 2069 markers = (Collection) this.backgroundRangeMarkers.get( 2070 new Integer(index)); 2071 if (markers == null) { 2072 markers = new java.util.ArrayList(); 2073 this.backgroundRangeMarkers.put(new Integer(index), markers); 2074 } 2075 markers.add(marker); 2076 } 2077 marker.addChangeListener(this); 2078 notifyListeners(new PlotChangeEvent(this)); 2079 } 2080 2081 /** 2082 * Clears all the range markers for the plot and sends a 2083 * {@link PlotChangeEvent} to all registered listeners. 2084 * 2085 * @see #clearDomainMarkers() 2086 */ 2087 public void clearRangeMarkers() { 2088 if (this.backgroundRangeMarkers != null) { 2089 Set keys = this.backgroundRangeMarkers.keySet(); 2090 Iterator iterator = keys.iterator(); 2091 while (iterator.hasNext()) { 2092 Integer key = (Integer) iterator.next(); 2093 clearRangeMarkers(key.intValue()); 2094 } 2095 this.backgroundRangeMarkers.clear(); 2096 } 2097 if (this.foregroundRangeMarkers != null) { 2098 Set keys = this.foregroundRangeMarkers.keySet(); 2099 Iterator iterator = keys.iterator(); 2100 while (iterator.hasNext()) { 2101 Integer key = (Integer) iterator.next(); 2102 clearRangeMarkers(key.intValue()); 2103 } 2104 this.foregroundRangeMarkers.clear(); 2105 } 2106 notifyListeners(new PlotChangeEvent(this)); 2107 } 2108 2109 /** 2110 * Returns the list of range markers (read only) for the specified layer. 2111 * 2112 * @param layer the layer (foreground or background). 2113 * 2114 * @return The list of range markers. 2115 * 2116 * @see #getRangeMarkers(int, Layer) 2117 */ 2118 public Collection getRangeMarkers(Layer layer) { 2119 return getRangeMarkers(0, layer); 2120 } 2121 2122 /** 2123 * Returns a collection of range markers for a particular renderer and 2124 * layer. 2125 * 2126 * @param index the renderer index. 2127 * @param layer the layer. 2128 * 2129 * @return A collection of markers (possibly <code>null</code>). 2130 */ 2131 public Collection getRangeMarkers(int index, Layer layer) { 2132 Collection result = null; 2133 Integer key = new Integer(index); 2134 if (layer == Layer.FOREGROUND) { 2135 result = (Collection) this.foregroundRangeMarkers.get(key); 2136 } 2137 else if (layer == Layer.BACKGROUND) { 2138 result = (Collection) this.backgroundRangeMarkers.get(key); 2139 } 2140 if (result != null) { 2141 result = Collections.unmodifiableCollection(result); 2142 } 2143 return result; 2144 } 2145 2146 /** 2147 * Clears all the range markers for the specified renderer. 2148 * 2149 * @param index the renderer index. 2150 * 2151 * @see #clearDomainMarkers(int) 2152 */ 2153 public void clearRangeMarkers(int index) { 2154 Integer key = new Integer(index); 2155 if (this.backgroundRangeMarkers != null) { 2156 Collection markers 2157 = (Collection) this.backgroundRangeMarkers.get(key); 2158 if (markers != null) { 2159 Iterator iterator = markers.iterator(); 2160 while (iterator.hasNext()) { 2161 Marker m = (Marker) iterator.next(); 2162 m.removeChangeListener(this); 2163 } 2164 markers.clear(); 2165 } 2166 } 2167 if (this.foregroundRangeMarkers != null) { 2168 Collection markers 2169 = (Collection) this.foregroundRangeMarkers.get(key); 2170 if (markers != null) { 2171 Iterator iterator = markers.iterator(); 2172 while (iterator.hasNext()) { 2173 Marker m = (Marker) iterator.next(); 2174 m.removeChangeListener(this); 2175 } 2176 markers.clear(); 2177 } 2178 } 2179 notifyListeners(new PlotChangeEvent(this)); 2180 } 2181 2182 /** 2183 * Returns a flag indicating whether or not the range crosshair is visible. 2184 * 2185 * @return The flag. 2186 * 2187 * @see #setRangeCrosshairVisible(boolean) 2188 */ 2189 public boolean isRangeCrosshairVisible() { 2190 return this.rangeCrosshairVisible; 2191 } 2192 2193 /** 2194 * Sets the flag indicating whether or not the range crosshair is visible. 2195 * 2196 * @param flag the new value of the flag. 2197 * 2198 * @see #isRangeCrosshairVisible() 2199 */ 2200 public void setRangeCrosshairVisible(boolean flag) { 2201 if (this.rangeCrosshairVisible != flag) { 2202 this.rangeCrosshairVisible = flag; 2203 notifyListeners(new PlotChangeEvent(this)); 2204 } 2205 } 2206 2207 /** 2208 * Returns a flag indicating whether or not the crosshair should "lock-on" 2209 * to actual data values. 2210 * 2211 * @return The flag. 2212 * 2213 * @see #setRangeCrosshairLockedOnData(boolean) 2214 */ 2215 public boolean isRangeCrosshairLockedOnData() { 2216 return this.rangeCrosshairLockedOnData; 2217 } 2218 2219 /** 2220 * Sets the flag indicating whether or not the range crosshair should 2221 * "lock-on" to actual data values. 2222 * 2223 * @param flag the flag. 2224 * 2225 * @see #isRangeCrosshairLockedOnData() 2226 */ 2227 public void setRangeCrosshairLockedOnData(boolean flag) { 2228 2229 if (this.rangeCrosshairLockedOnData != flag) { 2230 this.rangeCrosshairLockedOnData = flag; 2231 notifyListeners(new PlotChangeEvent(this)); 2232 } 2233 2234 } 2235 2236 /** 2237 * Returns the range crosshair value. 2238 * 2239 * @return The value. 2240 * 2241 * @see #setRangeCrosshairValue(double) 2242 */ 2243 public double getRangeCrosshairValue() { 2244 return this.rangeCrosshairValue; 2245 } 2246 2247 /** 2248 * Sets the domain crosshair value. 2249 * <P> 2250 * Registered listeners are notified that the plot has been modified, but 2251 * only if the crosshair is visible. 2252 * 2253 * @param value the new value. 2254 * 2255 * @see #getRangeCrosshairValue() 2256 */ 2257 public void setRangeCrosshairValue(double value) { 2258 setRangeCrosshairValue(value, true); 2259 } 2260 2261 /** 2262 * Sets the range crosshair value and, if requested, sends a 2263 * {@link PlotChangeEvent} to all registered listeners (but only if the 2264 * crosshair is visible). 2265 * 2266 * @param value the new value. 2267 * @param notify a flag that controls whether or not listeners are 2268 * notified. 2269 * 2270 * @see #getRangeCrosshairValue() 2271 */ 2272 public void setRangeCrosshairValue(double value, boolean notify) { 2273 this.rangeCrosshairValue = value; 2274 if (isRangeCrosshairVisible() && notify) { 2275 notifyListeners(new PlotChangeEvent(this)); 2276 } 2277 } 2278 2279 /** 2280 * Returns the pen-style (<code>Stroke</code>) used to draw the crosshair 2281 * (if visible). 2282 * 2283 * @return The crosshair stroke (never <code>null</code>). 2284 * 2285 * @see #setRangeCrosshairStroke(Stroke) 2286 * @see #isRangeCrosshairVisible() 2287 * @see #getRangeCrosshairPaint() 2288 */ 2289 public Stroke getRangeCrosshairStroke() { 2290 return this.rangeCrosshairStroke; 2291 } 2292 2293 /** 2294 * Sets the pen-style (<code>Stroke</code>) used to draw the range 2295 * crosshair (if visible), and sends a {@link PlotChangeEvent} to all 2296 * registered listeners. 2297 * 2298 * @param stroke the new crosshair stroke (<code>null</code> not 2299 * permitted). 2300 * 2301 * @see #getRangeCrosshairStroke() 2302 */ 2303 public void setRangeCrosshairStroke(Stroke stroke) { 2304 if (stroke == null) { 2305 throw new IllegalArgumentException("Null 'stroke' argument."); 2306 } 2307 this.rangeCrosshairStroke = stroke; 2308 notifyListeners(new PlotChangeEvent(this)); 2309 } 2310 2311 /** 2312 * Returns the paint used to draw the range crosshair. 2313 * 2314 * @return The paint (never <code>null</code>). 2315 * 2316 * @see #setRangeCrosshairPaint(Paint) 2317 * @see #isRangeCrosshairVisible() 2318 * @see #getRangeCrosshairStroke() 2319 */ 2320 public Paint getRangeCrosshairPaint() { 2321 return this.rangeCrosshairPaint; 2322 } 2323 2324 /** 2325 * Sets the paint used to draw the range crosshair (if visible) and 2326 * sends a {@link PlotChangeEvent} to all registered listeners. 2327 * 2328 * @param paint the paint (<code>null</code> not permitted). 2329 * 2330 * @see #getRangeCrosshairPaint() 2331 */ 2332 public void setRangeCrosshairPaint(Paint paint) { 2333 if (paint == null) { 2334 throw new IllegalArgumentException("Null 'paint' argument."); 2335 } 2336 this.rangeCrosshairPaint = paint; 2337 notifyListeners(new PlotChangeEvent(this)); 2338 } 2339 2340 /** 2341 * Returns the list of annotations. 2342 * 2343 * @return The list of annotations. 2344 */ 2345 public List getAnnotations() { 2346 return this.annotations; 2347 } 2348 2349 /** 2350 * Adds an annotation to the plot and sends a {@link PlotChangeEvent} to all 2351 * registered listeners. 2352 * 2353 * @param annotation the annotation (<code>null</code> not permitted). 2354 * 2355 * @see #removeAnnotation(CategoryAnnotation) 2356 */ 2357 public void addAnnotation(CategoryAnnotation annotation) { 2358 if (annotation == null) { 2359 throw new IllegalArgumentException("Null 'annotation' argument."); 2360 } 2361 this.annotations.add(annotation); 2362 notifyListeners(new PlotChangeEvent(this)); 2363 } 2364 2365 /** 2366 * Removes an annotation from the plot and sends a {@link PlotChangeEvent} 2367 * to all registered listeners. 2368 * 2369 * @param annotation the annotation (<code>null</code> not permitted). 2370 * 2371 * @return A boolean (indicates whether or not the annotation was removed). 2372 * 2373 * @see #addAnnotation(CategoryAnnotation) 2374 */ 2375 public boolean removeAnnotation(CategoryAnnotation annotation) { 2376 if (annotation == null) { 2377 throw new IllegalArgumentException("Null 'annotation' argument."); 2378 } 2379 boolean removed = this.annotations.remove(annotation); 2380 if (removed) { 2381 notifyListeners(new PlotChangeEvent(this)); 2382 } 2383 return removed; 2384 } 2385 2386 /** 2387 * Clears all the annotations and sends a {@link PlotChangeEvent} to all 2388 * registered listeners. 2389 */ 2390 public void clearAnnotations() { 2391 this.annotations.clear(); 2392 notifyListeners(new PlotChangeEvent(this)); 2393 } 2394 2395 /** 2396 * Calculates the space required for the domain axis/axes. 2397 * 2398 * @param g2 the graphics device. 2399 * @param plotArea the plot area. 2400 * @param space a carrier for the result (<code>null</code> permitted). 2401 * 2402 * @return The required space. 2403 */ 2404 protected AxisSpace calculateDomainAxisSpace(Graphics2D g2, 2405 Rectangle2D plotArea, 2406 AxisSpace space) { 2407 2408 if (space == null) { 2409 space = new AxisSpace(); 2410 } 2411 2412 // reserve some space for the domain axis... 2413 if (this.fixedDomainAxisSpace != null) { 2414 if (this.orientation == PlotOrientation.HORIZONTAL) { 2415 space.ensureAtLeast( 2416 this.fixedDomainAxisSpace.getLeft(), RectangleEdge.LEFT); 2417 space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(), 2418 RectangleEdge.RIGHT); 2419 } 2420 else if (this.orientation == PlotOrientation.VERTICAL) { 2421 space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(), 2422 RectangleEdge.TOP); 2423 space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(), 2424 RectangleEdge.BOTTOM); 2425 } 2426 } 2427 else { 2428 // reserve space for the primary domain axis... 2429 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 2430 getDomainAxisLocation(), this.orientation); 2431 if (this.drawSharedDomainAxis) { 2432 space = getDomainAxis().reserveSpace(g2, this, plotArea, 2433 domainEdge, space); 2434 } 2435 2436 // reserve space for any domain axes... 2437 for (int i = 0; i < this.domainAxes.size(); i++) { 2438 Axis xAxis = (Axis) this.domainAxes.get(i); 2439 if (xAxis != null) { 2440 RectangleEdge edge = getDomainAxisEdge(i); 2441 space = xAxis.reserveSpace(g2, this, plotArea, edge, space); 2442 } 2443 } 2444 } 2445 2446 return space; 2447 2448 } 2449 2450 /** 2451 * Calculates the space required for the range axis/axes. 2452 * 2453 * @param g2 the graphics device. 2454 * @param plotArea the plot area. 2455 * @param space a carrier for the result (<code>null</code> permitted). 2456 * 2457 * @return The required space. 2458 */ 2459 protected AxisSpace calculateRangeAxisSpace(Graphics2D g2, 2460 Rectangle2D plotArea, 2461 AxisSpace space) { 2462 2463 if (space == null) { 2464 space = new AxisSpace(); 2465 } 2466 2467 // reserve some space for the range axis... 2468 if (this.fixedRangeAxisSpace != null) { 2469 if (this.orientation == PlotOrientation.HORIZONTAL) { 2470 space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(), 2471 RectangleEdge.TOP); 2472 space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(), 2473 RectangleEdge.BOTTOM); 2474 } 2475 else if (this.orientation == PlotOrientation.VERTICAL) { 2476 space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(), 2477 RectangleEdge.LEFT); 2478 space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(), 2479 RectangleEdge.RIGHT); 2480 } 2481 } 2482 else { 2483 // reserve space for the range axes (if any)... 2484 for (int i = 0; i < this.rangeAxes.size(); i++) { 2485 Axis yAxis = (Axis) this.rangeAxes.get(i); 2486 if (yAxis != null) { 2487 RectangleEdge edge = getRangeAxisEdge(i); 2488 space = yAxis.reserveSpace(g2, this, plotArea, edge, space); 2489 } 2490 } 2491 } 2492 return space; 2493 2494 } 2495 2496 /** 2497 * Calculates the space required for the axes. 2498 * 2499 * @param g2 the graphics device. 2500 * @param plotArea the plot area. 2501 * 2502 * @return The space required for the axes. 2503 */ 2504 protected AxisSpace calculateAxisSpace(Graphics2D g2, 2505 Rectangle2D plotArea) { 2506 AxisSpace space = new AxisSpace(); 2507 space = calculateRangeAxisSpace(g2, plotArea, space); 2508 space = calculateDomainAxisSpace(g2, plotArea, space); 2509 return space; 2510 } 2511 2512 /** 2513 * Draws the plot on a Java 2D graphics device (such as the screen or a 2514 * printer). 2515 * <P> 2516 * At your option, you may supply an instance of {@link PlotRenderingInfo}. 2517 * If you do, it will be populated with information about the drawing, 2518 * including various plot dimensions and tooltip info. 2519 * 2520 * @param g2 the graphics device. 2521 * @param area the area within which the plot (including axes) should 2522 * be drawn. 2523 * @param anchor the anchor point (<code>null</code> permitted). 2524 * @param parentState the state from the parent plot, if there is one. 2525 * @param state collects info as the chart is drawn (possibly 2526 * <code>null</code>). 2527 */ 2528 public void draw(Graphics2D g2, Rectangle2D area, 2529 Point2D anchor, 2530 PlotState parentState, 2531 PlotRenderingInfo state) { 2532 2533 // if the plot area is too small, just return... 2534 boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); 2535 boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); 2536 if (b1 || b2) { 2537 return; 2538 } 2539 2540 // record the plot area... 2541 if (state == null) { 2542 // if the incoming state is null, no information will be passed 2543 // back to the caller - but we create a temporary state to record 2544 // the plot area, since that is used later by the axes 2545 state = new PlotRenderingInfo(null); 2546 } 2547 state.setPlotArea(area); 2548 2549 // adjust the drawing area for the plot insets (if any)... 2550 RectangleInsets insets = getInsets(); 2551 insets.trim(area); 2552 2553 // calculate the data area... 2554 AxisSpace space = calculateAxisSpace(g2, area); 2555 Rectangle2D dataArea = space.shrink(area, null); 2556 this.axisOffset.trim(dataArea); 2557 2558 state.setDataArea(dataArea); 2559 2560 // if there is a renderer, it draws the background, otherwise use the 2561 // default background... 2562 if (getRenderer() != null) { 2563 getRenderer().drawBackground(g2, this, dataArea); 2564 } 2565 else { 2566 drawBackground(g2, dataArea); 2567 } 2568 2569 Map axisStateMap = drawAxes(g2, area, dataArea, state); 2570 2571 // don't let anyone draw outside the data area 2572 Shape savedClip = g2.getClip(); 2573 g2.clip(dataArea); 2574 2575 drawDomainGridlines(g2, dataArea); 2576 2577 AxisState rangeAxisState = (AxisState) axisStateMap.get(getRangeAxis()); 2578 if (rangeAxisState == null) { 2579 if (parentState != null) { 2580 rangeAxisState = (AxisState) parentState.getSharedAxisStates() 2581 .get(getRangeAxis()); 2582 } 2583 } 2584 if (rangeAxisState != null) { 2585 drawRangeGridlines(g2, dataArea, rangeAxisState.getTicks()); 2586 } 2587 2588 // draw the markers... 2589 for (int i = 0; i < this.renderers.size(); i++) { 2590 drawDomainMarkers(g2, dataArea, i, Layer.BACKGROUND); 2591 } 2592 for (int i = 0; i < this.renderers.size(); i++) { 2593 drawRangeMarkers(g2, dataArea, i, Layer.BACKGROUND); 2594 } 2595 2596 // now render data items... 2597 boolean foundData = false; 2598 2599 // set up the alpha-transparency... 2600 Composite originalComposite = g2.getComposite(); 2601 g2.setComposite(AlphaComposite.getInstance( 2602 AlphaComposite.SRC_OVER, getForegroundAlpha())); 2603 2604 DatasetRenderingOrder order = getDatasetRenderingOrder(); 2605 if (order == DatasetRenderingOrder.FORWARD) { 2606 for (int i = 0; i < this.datasets.size(); i++) { 2607 foundData = render(g2, dataArea, i, state) || foundData; 2608 } 2609 } 2610 else { // DatasetRenderingOrder.REVERSE 2611 for (int i = this.datasets.size() - 1; i >= 0; i--) { 2612 foundData = render(g2, dataArea, i, state) || foundData; 2613 } 2614 } 2615 // draw the foreground markers... 2616 for (int i = 0; i < this.renderers.size(); i++) { 2617 drawDomainMarkers(g2, dataArea, i, Layer.FOREGROUND); 2618 } 2619 for (int i = 0; i < this.renderers.size(); i++) { 2620 drawRangeMarkers(g2, dataArea, i, Layer.FOREGROUND); 2621 } 2622 2623 // draw the annotations (if any)... 2624 drawAnnotations(g2, dataArea); 2625 2626 g2.setClip(savedClip); 2627 g2.setComposite(originalComposite); 2628 2629 if (!foundData) { 2630 drawNoDataMessage(g2, dataArea); 2631 } 2632 2633 // draw range crosshair if required... 2634 if (isRangeCrosshairVisible()) { 2635 // FIXME: this doesn't handle multiple range axes 2636 drawRangeCrosshair(g2, dataArea, getOrientation(), 2637 getRangeCrosshairValue(), getRangeAxis(), 2638 getRangeCrosshairStroke(), getRangeCrosshairPaint()); 2639 } 2640 2641 // draw an outline around the plot area... 2642 if (getRenderer() != null) { 2643 getRenderer().drawOutline(g2, this, dataArea); 2644 } 2645 else { 2646 drawOutline(g2, dataArea); 2647 } 2648 2649 } 2650 2651 /** 2652 * A utility method for drawing the plot's axes. 2653 * 2654 * @param g2 the graphics device. 2655 * @param plotArea the plot area. 2656 * @param dataArea the data area. 2657 * @param plotState collects information about the plot (<code>null</code> 2658 * permitted). 2659 * 2660 * @return A map containing the axis states. 2661 */ 2662 protected Map drawAxes(Graphics2D g2, 2663 Rectangle2D plotArea, 2664 Rectangle2D dataArea, 2665 PlotRenderingInfo plotState) { 2666 2667 AxisCollection axisCollection = new AxisCollection(); 2668 2669 // add domain axes to lists... 2670 for (int index = 0; index < this.domainAxes.size(); index++) { 2671 CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(index); 2672 if (xAxis != null) { 2673 axisCollection.add(xAxis, getDomainAxisEdge(index)); 2674 } 2675 } 2676 2677 // add range axes to lists... 2678 for (int index = 0; index < this.rangeAxes.size(); index++) { 2679 ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index); 2680 if (yAxis != null) { 2681 axisCollection.add(yAxis, getRangeAxisEdge(index)); 2682 } 2683 } 2684 2685 Map axisStateMap = new HashMap(); 2686 2687 // draw the top axes 2688 double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset( 2689 dataArea.getHeight()); 2690 Iterator iterator = axisCollection.getAxesAtTop().iterator(); 2691 while (iterator.hasNext()) { 2692 Axis axis = (Axis) iterator.next(); 2693 if (axis != null) { 2694 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, 2695 RectangleEdge.TOP, plotState); 2696 cursor = axisState.getCursor(); 2697 axisStateMap.put(axis, axisState); 2698 } 2699 } 2700 2701 // draw the bottom axes 2702 cursor = dataArea.getMaxY() 2703 + this.axisOffset.calculateBottomOutset(dataArea.getHeight()); 2704 iterator = axisCollection.getAxesAtBottom().iterator(); 2705 while (iterator.hasNext()) { 2706 Axis axis = (Axis) iterator.next(); 2707 if (axis != null) { 2708 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, 2709 RectangleEdge.BOTTOM, plotState); 2710 cursor = axisState.getCursor(); 2711 axisStateMap.put(axis, axisState); 2712 } 2713 } 2714 2715 // draw the left axes 2716 cursor = dataArea.getMinX() 2717 - this.axisOffset.calculateLeftOutset(dataArea.getWidth()); 2718 iterator = axisCollection.getAxesAtLeft().iterator(); 2719 while (iterator.hasNext()) { 2720 Axis axis = (Axis) iterator.next(); 2721 if (axis != null) { 2722 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, 2723 RectangleEdge.LEFT, plotState); 2724 cursor = axisState.getCursor(); 2725 axisStateMap.put(axis, axisState); 2726 } 2727 } 2728 2729 // draw the right axes 2730 cursor = dataArea.getMaxX() 2731 + this.axisOffset.calculateRightOutset(dataArea.getWidth()); 2732 iterator = axisCollection.getAxesAtRight().iterator(); 2733 while (iterator.hasNext()) { 2734 Axis axis = (Axis) iterator.next(); 2735 if (axis != null) { 2736 AxisState axisState = axis.draw(g2, cursor, plotArea, dataArea, 2737 RectangleEdge.RIGHT, plotState); 2738 cursor = axisState.getCursor(); 2739 axisStateMap.put(axis, axisState); 2740 } 2741 } 2742 2743 return axisStateMap; 2744 2745 } 2746 2747 /** 2748 * Draws a representation of a dataset within the dataArea region using the 2749 * appropriate renderer. 2750 * 2751 * @param g2 the graphics device. 2752 * @param dataArea the region in which the data is to be drawn. 2753 * @param index the dataset and renderer index. 2754 * @param info an optional object for collection dimension information. 2755 * 2756 * @return A boolean that indicates whether or not real data was found. 2757 */ 2758 public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, 2759 PlotRenderingInfo info) { 2760 2761 boolean foundData = false; 2762 CategoryDataset currentDataset = getDataset(index); 2763 CategoryItemRenderer renderer = getRenderer(index); 2764 CategoryAxis domainAxis = getDomainAxisForDataset(index); 2765 ValueAxis rangeAxis = getRangeAxisForDataset(index); 2766 boolean hasData = !DatasetUtilities.isEmptyOrNull(currentDataset); 2767 if (hasData && renderer != null) { 2768 2769 foundData = true; 2770 CategoryItemRendererState state = renderer.initialise(g2, dataArea, 2771 this, index, info); 2772 int columnCount = currentDataset.getColumnCount(); 2773 int rowCount = currentDataset.getRowCount(); 2774 int passCount = renderer.getPassCount(); 2775 for (int pass = 0; pass < passCount; pass++) { 2776 if (this.columnRenderingOrder == SortOrder.ASCENDING) { 2777 for (int column = 0; column < columnCount; column++) { 2778 if (this.rowRenderingOrder == SortOrder.ASCENDING) { 2779 for (int row = 0; row < rowCount; row++) { 2780 renderer.drawItem(g2, state, dataArea, this, 2781 domainAxis, rangeAxis, currentDataset, 2782 row, column, pass); 2783 } 2784 } 2785 else { 2786 for (int row = rowCount - 1; row >= 0; row--) { 2787 renderer.drawItem(g2, state, dataArea, this, 2788 domainAxis, rangeAxis, currentDataset, 2789 row, column, pass); 2790 } 2791 } 2792 } 2793 } 2794 else { 2795 for (int column = columnCount - 1; column >= 0; column--) { 2796 if (this.rowRenderingOrder == SortOrder.ASCENDING) { 2797 for (int row = 0; row < rowCount; row++) { 2798 renderer.drawItem(g2, state, dataArea, this, 2799 domainAxis, rangeAxis, currentDataset, 2800 row, column, pass); 2801 } 2802 } 2803 else { 2804 for (int row = rowCount - 1; row >= 0; row--) { 2805 renderer.drawItem(g2, state, dataArea, this, 2806 domainAxis, rangeAxis, currentDataset, 2807 row, column, pass); 2808 } 2809 } 2810 } 2811 } 2812 } 2813 } 2814 return foundData; 2815 2816 } 2817 2818 /** 2819 * Draws the gridlines for the plot. 2820 * 2821 * @param g2 the graphics device. 2822 * @param dataArea the area inside the axes. 2823 * 2824 * @see #drawRangeGridlines(Graphics2D, Rectangle2D, List) 2825 */ 2826 protected void drawDomainGridlines(Graphics2D g2, Rectangle2D dataArea) { 2827 2828 // draw the domain grid lines, if any... 2829 if (isDomainGridlinesVisible()) { 2830 CategoryAnchor anchor = getDomainGridlinePosition(); 2831 RectangleEdge domainAxisEdge = getDomainAxisEdge(); 2832 Stroke gridStroke = getDomainGridlineStroke(); 2833 Paint gridPaint = getDomainGridlinePaint(); 2834 if ((gridStroke != null) && (gridPaint != null)) { 2835 // iterate over the categories 2836 CategoryDataset data = getDataset(); 2837 if (data != null) { 2838 CategoryAxis axis = getDomainAxis(); 2839 if (axis != null) { 2840 int columnCount = data.getColumnCount(); 2841 for (int c = 0; c < columnCount; c++) { 2842 double xx = axis.getCategoryJava2DCoordinate( 2843 anchor, c, columnCount, dataArea, 2844 domainAxisEdge); 2845 CategoryItemRenderer renderer1 = getRenderer(); 2846 if (renderer1 != null) { 2847 renderer1.drawDomainGridline(g2, this, 2848 dataArea, xx); 2849 } 2850 } 2851 } 2852 } 2853 } 2854 } 2855 } 2856 2857 /** 2858 * Draws the gridlines for the plot. 2859 * 2860 * @param g2 the graphics device. 2861 * @param dataArea the area inside the axes. 2862 * @param ticks the ticks. 2863 * 2864 * @see #drawDomainGridlines(Graphics2D, Rectangle2D) 2865 */ 2866 protected void drawRangeGridlines(Graphics2D g2, Rectangle2D dataArea, 2867 List ticks) { 2868 // draw the range grid lines, if any... 2869 if (isRangeGridlinesVisible()) { 2870 Stroke gridStroke = getRangeGridlineStroke(); 2871 Paint gridPaint = getRangeGridlinePaint(); 2872 if ((gridStroke != null) && (gridPaint != null)) { 2873 ValueAxis axis = getRangeAxis(); 2874 if (axis != null) { 2875 Iterator iterator = ticks.iterator(); 2876 while (iterator.hasNext()) { 2877 ValueTick tick = (ValueTick) iterator.next(); 2878 CategoryItemRenderer renderer1 = getRenderer(); 2879 if (renderer1 != null) { 2880 renderer1.drawRangeGridline(g2, this, 2881 getRangeAxis(), dataArea, tick.getValue()); 2882 } 2883 } 2884 } 2885 } 2886 } 2887 } 2888 2889 /** 2890 * Draws the annotations... 2891 * 2892 * @param g2 the graphics device. 2893 * @param dataArea the data area. 2894 */ 2895 protected void drawAnnotations(Graphics2D g2, Rectangle2D dataArea) { 2896 2897 if (getAnnotations() != null) { 2898 Iterator iterator = getAnnotations().iterator(); 2899 while (iterator.hasNext()) { 2900 CategoryAnnotation annotation 2901 = (CategoryAnnotation) iterator.next(); 2902 annotation.draw(g2, this, dataArea, getDomainAxis(), 2903 getRangeAxis()); 2904 } 2905 } 2906 2907 } 2908 2909 /** 2910 * Draws the domain markers (if any) for an axis and layer. This method is 2911 * typically called from within the draw() method. 2912 * 2913 * @param g2 the graphics device. 2914 * @param dataArea the data area. 2915 * @param index the renderer index. 2916 * @param layer the layer (foreground or background). 2917 * 2918 * @see #drawRangeMarkers(Graphics2D, Rectangle2D, int, Layer) 2919 */ 2920 protected void drawDomainMarkers(Graphics2D g2, Rectangle2D dataArea, 2921 int index, Layer layer) { 2922 2923 CategoryItemRenderer r = getRenderer(index); 2924 if (r == null) { 2925 return; 2926 } 2927 2928 Collection markers = getDomainMarkers(index, layer); 2929 CategoryAxis axis = getDomainAxisForDataset(index); 2930 if (markers != null && axis != null) { 2931 Iterator iterator = markers.iterator(); 2932 while (iterator.hasNext()) { 2933 CategoryMarker marker = (CategoryMarker) iterator.next(); 2934 r.drawDomainMarker(g2, this, axis, marker, dataArea); 2935 } 2936 } 2937 2938 } 2939 2940 /** 2941 * Draws the range markers (if any) for an axis and layer. This method is 2942 * typically called from within the draw() method. 2943 * 2944 * @param g2 the graphics device. 2945 * @param dataArea the data area. 2946 * @param index the renderer index. 2947 * @param layer the layer (foreground or background). 2948 * 2949 * @see #drawDomainMarkers(Graphics2D, Rectangle2D, int, Layer) 2950 */ 2951 protected void drawRangeMarkers(Graphics2D g2, Rectangle2D dataArea, 2952 int index, Layer layer) { 2953 2954 CategoryItemRenderer r = getRenderer(index); 2955 if (r == null) { 2956 return; 2957 } 2958 2959 Collection markers = getRangeMarkers(index, layer); 2960 ValueAxis axis = getRangeAxisForDataset(index); 2961 if (markers != null && axis != null) { 2962 Iterator iterator = markers.iterator(); 2963 while (iterator.hasNext()) { 2964 Marker marker = (Marker) iterator.next(); 2965 r.drawRangeMarker(g2, this, axis, marker, dataArea); 2966 } 2967 } 2968 2969 } 2970 2971 /** 2972 * Utility method for drawing a line perpendicular to the range axis (used 2973 * for crosshairs). 2974 * 2975 * @param g2 the graphics device. 2976 * @param dataArea the area defined by the axes. 2977 * @param value the data value. 2978 * @param stroke the line stroke (<code>null</code> not permitted). 2979 * @param paint the line paint (<code>null</code> not permitted). 2980 */ 2981 protected void drawRangeLine(Graphics2D g2, Rectangle2D dataArea, 2982 double value, Stroke stroke, Paint paint) { 2983 2984 double java2D = getRangeAxis().valueToJava2D(value, dataArea, 2985 getRangeAxisEdge()); 2986 Line2D line = null; 2987 if (this.orientation == PlotOrientation.HORIZONTAL) { 2988 line = new Line2D.Double(java2D, dataArea.getMinY(), java2D, 2989 dataArea.getMaxY()); 2990 } 2991 else if (this.orientation == PlotOrientation.VERTICAL) { 2992 line = new Line2D.Double(dataArea.getMinX(), java2D, 2993 dataArea.getMaxX(), java2D); 2994 } 2995 g2.setStroke(stroke); 2996 g2.setPaint(paint); 2997 g2.draw(line); 2998 2999 } 3000 3001 /** 3002 * Draws a range crosshair. 3003 * 3004 * @param g2 the graphics target. 3005 * @param dataArea the data area. 3006 * @param orientation the plot orientation. 3007 * @param value the crosshair value. 3008 * @param axis the axis against which the value is measured. 3009 * @param stroke the stroke used to draw the crosshair line. 3010 * @param paint the paint used to draw the crosshair line. 3011 * 3012 * @since 1.0.5 3013 */ 3014 protected void drawRangeCrosshair(Graphics2D g2, Rectangle2D dataArea, 3015 PlotOrientation orientation, double value, ValueAxis axis, 3016 Stroke stroke, Paint paint) { 3017 3018 if (!axis.getRange().contains(value)) { 3019 return; 3020 } 3021 Line2D line = null; 3022 if (orientation == PlotOrientation.HORIZONTAL) { 3023 double xx = axis.valueToJava2D(value, dataArea, 3024 RectangleEdge.BOTTOM); 3025 line = new Line2D.Double(xx, dataArea.getMinY(), xx, 3026 dataArea.getMaxY()); 3027 } 3028 else { 3029 double yy = axis.valueToJava2D(value, dataArea, 3030 RectangleEdge.LEFT); 3031 line = new Line2D.Double(dataArea.getMinX(), yy, 3032 dataArea.getMaxX(), yy); 3033 } 3034 g2.setStroke(stroke); 3035 g2.setPaint(paint); 3036 g2.draw(line); 3037 3038 } 3039 3040 /** 3041 * Returns the range of data values that will be plotted against the range 3042 * axis. If the dataset is <code>null</code>, this method returns 3043 * <code>null</code>. 3044 * 3045 * @param axis the axis. 3046 * 3047 * @return The data range. 3048 */ 3049 public Range getDataRange(ValueAxis axis) { 3050 3051 Range result = null; 3052 List mappedDatasets = new ArrayList(); 3053 3054 int rangeIndex = this.rangeAxes.indexOf(axis); 3055 if (rangeIndex >= 0) { 3056 mappedDatasets.addAll(datasetsMappedToRangeAxis(rangeIndex)); 3057 } 3058 else if (axis == getRangeAxis()) { 3059 mappedDatasets.addAll(datasetsMappedToRangeAxis(0)); 3060 } 3061 3062 // iterate through the datasets that map to the axis and get the union 3063 // of the ranges. 3064 Iterator iterator = mappedDatasets.iterator(); 3065 while (iterator.hasNext()) { 3066 CategoryDataset d = (CategoryDataset) iterator.next(); 3067 CategoryItemRenderer r = getRendererForDataset(d); 3068 if (r != null) { 3069 result = Range.combine(result, r.findRangeBounds(d)); 3070 } 3071 } 3072 return result; 3073 3074 } 3075 3076 /** 3077 * Returns a list of the datasets that are mapped to the axis with the 3078 * specified index. 3079 * 3080 * @param axisIndex the axis index. 3081 * 3082 * @return The list (possibly empty, but never <code>null</code>). 3083 * 3084 * @since 1.0.3 3085 */ 3086 private List datasetsMappedToDomainAxis(int axisIndex) { 3087 List result = new ArrayList(); 3088 for (int datasetIndex = 0; datasetIndex < this.datasets.size(); 3089 datasetIndex++) { 3090 Object dataset = this.datasets.get(datasetIndex); 3091 if (dataset != null) { 3092 Integer m = (Integer) this.datasetToDomainAxisMap.get( 3093 datasetIndex); 3094 if (m == null) { // a dataset with no mapping is assigned to 3095 // axis 0 3096 if (axisIndex == 0) { 3097 result.add(dataset); 3098 } 3099 } 3100 else { 3101 if (m.intValue() == axisIndex) { 3102 result.add(dataset); 3103 } 3104 } 3105 } 3106 } 3107 return result; 3108 } 3109 3110 /** 3111 * A utility method that returns a list of datasets that are mapped to a 3112 * given range axis. 3113 * 3114 * @param index the axis index. 3115 * 3116 * @return A list of datasets. 3117 */ 3118 private List datasetsMappedToRangeAxis(int index) { 3119 List result = new ArrayList(); 3120 for (int i = 0; i < this.datasets.size(); i++) { 3121 Object dataset = this.datasets.get(i); 3122 if (dataset != null) { 3123 Integer m = (Integer) this.datasetToRangeAxisMap.get(i); 3124 if (m == null) { // a dataset with no mapping is assigned to 3125 // axis 0 3126 if (index == 0) { 3127 result.add(dataset); 3128 } 3129 } 3130 else { 3131 if (m.intValue() == index) { 3132 result.add(dataset); 3133 } 3134 } 3135 } 3136 } 3137 return result; 3138 } 3139 3140 /** 3141 * Returns the weight for this plot when it is used as a subplot within a 3142 * combined plot. 3143 * 3144 * @return The weight. 3145 * 3146 * @see #setWeight(int) 3147 */ 3148 public int getWeight() { 3149 return this.weight; 3150 } 3151 3152 /** 3153 * Sets the weight for the plot. 3154 * 3155 * @param weight the weight. 3156 * 3157 * @see #getWeight() 3158 */ 3159 public void setWeight(int weight) { 3160 this.weight = weight; 3161 // TODO: notify? 3162 } 3163 3164 /** 3165 * Returns the fixed domain axis space. 3166 * 3167 * @return The fixed domain axis space (possibly <code>null</code>). 3168 * 3169 * @see #setFixedDomainAxisSpace(AxisSpace) 3170 */ 3171 public AxisSpace getFixedDomainAxisSpace() { 3172 return this.fixedDomainAxisSpace; 3173 } 3174 3175 /** 3176 * Sets the fixed domain axis space. 3177 * 3178 * @param space the space (<code>null</code> permitted). 3179 * 3180 * @see #getFixedDomainAxisSpace() 3181 */ 3182 public void setFixedDomainAxisSpace(AxisSpace space) { 3183 this.fixedDomainAxisSpace = space; 3184 // TODO: notify? 3185 } 3186 3187 /** 3188 * Returns the fixed range axis space. 3189 * 3190 * @return The fixed range axis space (possibly <code>null</code>). 3191 * 3192 * @see #setFixedRangeAxisSpace(AxisSpace) 3193 */ 3194 public AxisSpace getFixedRangeAxisSpace() { 3195 return this.fixedRangeAxisSpace; 3196 } 3197 3198 /** 3199 * Sets the fixed range axis space. 3200 * 3201 * @param space the space (<code>null</code> permitted). 3202 * 3203 * @see #getFixedRangeAxisSpace() 3204 */ 3205 public void setFixedRangeAxisSpace(AxisSpace space) { 3206 this.fixedRangeAxisSpace = space; 3207 // TODO: fire event? 3208 } 3209 3210 /** 3211 * Returns a list of the categories in the plot's primary dataset. 3212 * 3213 * @return A list of the categories in the plot's primary dataset. 3214 * 3215 * @see #getCategoriesForAxis(CategoryAxis) 3216 */ 3217 public List getCategories() { 3218 List result = null; 3219 if (getDataset() != null) { 3220 result = Collections.unmodifiableList(getDataset().getColumnKeys()); 3221 } 3222 return result; 3223 } 3224 3225 /** 3226 * Returns a list of the categories that should be displayed for the 3227 * specified axis. 3228 * 3229 * @param axis the axis (<code>null</code> not permitted) 3230 * 3231 * @return The categories. 3232 * 3233 * @since 1.0.3 3234 */ 3235 public List getCategoriesForAxis(CategoryAxis axis) { 3236 List result = new ArrayList(); 3237 int axisIndex = this.domainAxes.indexOf(axis); 3238 List datasets = datasetsMappedToDomainAxis(axisIndex); 3239 Iterator iterator = datasets.iterator(); 3240 while (iterator.hasNext()) { 3241 CategoryDataset dataset = (CategoryDataset) iterator.next(); 3242 // add the unique categories from this dataset 3243 for (int i = 0; i < dataset.getColumnCount(); i++) { 3244 Comparable category = dataset.getColumnKey(i); 3245 if (!result.contains(category)) { 3246 result.add(category); 3247 } 3248 } 3249 } 3250 return result; 3251 } 3252 3253 /** 3254 * Returns the flag that controls whether or not the shared domain axis is 3255 * drawn for each subplot. 3256 * 3257 * @return A boolean. 3258 * 3259 * @see #setDrawSharedDomainAxis(boolean) 3260 */ 3261 public boolean getDrawSharedDomainAxis() { 3262 return this.drawSharedDomainAxis; 3263 } 3264 3265 /** 3266 * Sets the flag that controls whether the shared domain axis is drawn when 3267 * this plot is being used as a subplot. 3268 * 3269 * @param draw a boolean. 3270 * 3271 * @see #getDrawSharedDomainAxis() 3272 */ 3273 public void setDrawSharedDomainAxis(boolean draw) { 3274 this.drawSharedDomainAxis = draw; 3275 notifyListeners(new PlotChangeEvent(this)); 3276 } 3277 3278 /** 3279 * Returns <code>false</code> to indicate that the domain axes are not 3280 * zoomable. 3281 * 3282 * @return A boolean. 3283 * 3284 * @see #isRangeZoomable() 3285 */ 3286 public boolean isDomainZoomable() { 3287 return false; 3288 } 3289 3290 /** 3291 * Returns <code>true</code> to indicate that the range axes are zoomable. 3292 * 3293 * @return A boolean. 3294 * 3295 * @see #isDomainZoomable() 3296 */ 3297 public boolean isRangeZoomable() { 3298 return true; 3299 } 3300 3301 /** 3302 * This method does nothing, because <code>CategoryPlot</code> doesn't 3303 * support zooming on the domain. 3304 * 3305 * @param factor the zoom factor. 3306 * @param state the plot state. 3307 * @param source the source point (in Java2D space) for the zoom. 3308 */ 3309 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 3310 Point2D source) { 3311 // can't zoom domain axis 3312 } 3313 3314 /** 3315 * This method does nothing, because <code>CategoryPlot</code> doesn't 3316 * support zooming on the domain. 3317 * 3318 * @param lowerPercent the lower bound. 3319 * @param upperPercent the upper bound. 3320 * @param state the plot state. 3321 * @param source the source point (in Java2D space) for the zoom. 3322 */ 3323 public void zoomDomainAxes(double lowerPercent, double upperPercent, 3324 PlotRenderingInfo state, Point2D source) { 3325 // can't zoom domain axis 3326 } 3327 3328 /** 3329 * Multiplies the range on the range axis/axes by the specified factor. 3330 * 3331 * @param factor the zoom factor. 3332 * @param state the plot state. 3333 * @param source the source point (in Java2D space) for the zoom. 3334 */ 3335 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 3336 Point2D source) { 3337 for (int i = 0; i < this.rangeAxes.size(); i++) { 3338 ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i); 3339 if (rangeAxis != null) { 3340 rangeAxis.resizeRange(factor); 3341 } 3342 } 3343 } 3344 3345 /** 3346 * Zooms in on the range axes. 3347 * 3348 * @param lowerPercent the lower bound. 3349 * @param upperPercent the upper bound. 3350 * @param state the plot state. 3351 * @param source the source point (in Java2D space) for the zoom. 3352 */ 3353 public void zoomRangeAxes(double lowerPercent, double upperPercent, 3354 PlotRenderingInfo state, Point2D source) { 3355 for (int i = 0; i < this.rangeAxes.size(); i++) { 3356 ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i); 3357 if (rangeAxis != null) { 3358 rangeAxis.zoomRange(lowerPercent, upperPercent); 3359 } 3360 } 3361 } 3362 3363 /** 3364 * Returns the anchor value. 3365 * 3366 * @return The anchor value. 3367 * 3368 * @see #setAnchorValue(double) 3369 */ 3370 public double getAnchorValue() { 3371 return this.anchorValue; 3372 } 3373 3374 /** 3375 * Sets the anchor value and sends a {@link PlotChangeEvent} to all 3376 * registered listeners. 3377 * 3378 * @param value the anchor value. 3379 * 3380 * @see #getAnchorValue() 3381 */ 3382 public void setAnchorValue(double value) { 3383 setAnchorValue(value, true); 3384 } 3385 3386 /** 3387 * Sets the anchor value and, if requested, sends a {@link PlotChangeEvent} 3388 * to all registered listeners. 3389 * 3390 * @param value the value. 3391 * @param notify notify listeners? 3392 * 3393 * @see #getAnchorValue() 3394 */ 3395 public void setAnchorValue(double value, boolean notify) { 3396 this.anchorValue = value; 3397 if (notify) { 3398 notifyListeners(new PlotChangeEvent(this)); 3399 } 3400 } 3401 3402 /** 3403 * Tests the plot for equality with an arbitrary object. 3404 * 3405 * @param obj the object to test against (<code>null</code> permitted). 3406 * 3407 * @return A boolean. 3408 */ 3409 public boolean equals(Object obj) { 3410 3411 if (obj == this) { 3412 return true; 3413 } 3414 if (!(obj instanceof CategoryPlot)) { 3415 return false; 3416 } 3417 if (!super.equals(obj)) { 3418 return false; 3419 } 3420 3421 CategoryPlot that = (CategoryPlot) obj; 3422 3423 if (this.orientation != that.orientation) { 3424 return false; 3425 } 3426 if (!ObjectUtilities.equal(this.axisOffset, that.axisOffset)) { 3427 return false; 3428 } 3429 if (!this.domainAxes.equals(that.domainAxes)) { 3430 return false; 3431 } 3432 if (!this.domainAxisLocations.equals(that.domainAxisLocations)) { 3433 return false; 3434 } 3435 if (this.drawSharedDomainAxis != that.drawSharedDomainAxis) { 3436 return false; 3437 } 3438 if (!this.rangeAxes.equals(that.rangeAxes)) { 3439 return false; 3440 } 3441 if (!this.rangeAxisLocations.equals(that.rangeAxisLocations)) { 3442 return false; 3443 } 3444 if (!ObjectUtilities.equal(this.datasetToDomainAxisMap, 3445 that.datasetToDomainAxisMap)) { 3446 return false; 3447 } 3448 if (!ObjectUtilities.equal(this.datasetToRangeAxisMap, 3449 that.datasetToRangeAxisMap)) { 3450 return false; 3451 } 3452 if (!ObjectUtilities.equal(this.renderers, that.renderers)) { 3453 return false; 3454 } 3455 if (this.renderingOrder != that.renderingOrder) { 3456 return false; 3457 } 3458 if (this.columnRenderingOrder != that.columnRenderingOrder) { 3459 return false; 3460 } 3461 if (this.rowRenderingOrder != that.rowRenderingOrder) { 3462 return false; 3463 } 3464 if (this.domainGridlinesVisible != that.domainGridlinesVisible) { 3465 return false; 3466 } 3467 if (this.domainGridlinePosition != that.domainGridlinePosition) { 3468 return false; 3469 } 3470 if (!ObjectUtilities.equal(this.domainGridlineStroke, 3471 that.domainGridlineStroke)) { 3472 return false; 3473 } 3474 if (!PaintUtilities.equal(this.domainGridlinePaint, 3475 that.domainGridlinePaint)) { 3476 return false; 3477 } 3478 if (this.rangeGridlinesVisible != that.rangeGridlinesVisible) { 3479 return false; 3480 } 3481 if (!ObjectUtilities.equal(this.rangeGridlineStroke, 3482 that.rangeGridlineStroke)) { 3483 return false; 3484 } 3485 if (!PaintUtilities.equal(this.rangeGridlinePaint, 3486 that.rangeGridlinePaint)) { 3487 return false; 3488 } 3489 if (this.anchorValue != that.anchorValue) { 3490 return false; 3491 } 3492 if (this.rangeCrosshairVisible != that.rangeCrosshairVisible) { 3493 return false; 3494 } 3495 if (this.rangeCrosshairValue != that.rangeCrosshairValue) { 3496 return false; 3497 } 3498 if (!ObjectUtilities.equal(this.rangeCrosshairStroke, 3499 that.rangeCrosshairStroke)) { 3500 return false; 3501 } 3502 if (!PaintUtilities.equal(this.rangeCrosshairPaint, 3503 that.rangeCrosshairPaint)) { 3504 return false; 3505 } 3506 if (this.rangeCrosshairLockedOnData 3507 != that.rangeCrosshairLockedOnData) { 3508 return false; 3509 } 3510 if (!ObjectUtilities.equal(this.foregroundRangeMarkers, 3511 that.foregroundRangeMarkers)) { 3512 return false; 3513 } 3514 if (!ObjectUtilities.equal(this.backgroundRangeMarkers, 3515 that.backgroundRangeMarkers)) { 3516 return false; 3517 } 3518 if (!ObjectUtilities.equal(this.annotations, that.annotations)) { 3519 return false; 3520 } 3521 if (this.weight != that.weight) { 3522 return false; 3523 } 3524 if (!ObjectUtilities.equal(this.fixedDomainAxisSpace, 3525 that.fixedDomainAxisSpace)) { 3526 return false; 3527 } 3528 if (!ObjectUtilities.equal(this.fixedRangeAxisSpace, 3529 that.fixedRangeAxisSpace)) { 3530 return false; 3531 } 3532 3533 return true; 3534 3535 } 3536 3537 /** 3538 * Returns a clone of the plot. 3539 * 3540 * @return A clone. 3541 * 3542 * @throws CloneNotSupportedException if the cloning is not supported. 3543 */ 3544 public Object clone() throws CloneNotSupportedException { 3545 3546 CategoryPlot clone = (CategoryPlot) super.clone(); 3547 3548 clone.domainAxes = new ObjectList(); 3549 for (int i = 0; i < this.domainAxes.size(); i++) { 3550 CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i); 3551 if (xAxis != null) { 3552 CategoryAxis clonedAxis = (CategoryAxis) xAxis.clone(); 3553 clone.setDomainAxis(i, clonedAxis); 3554 } 3555 } 3556 clone.domainAxisLocations 3557 = (ObjectList) this.domainAxisLocations.clone(); 3558 3559 clone.rangeAxes = new ObjectList(); 3560 for (int i = 0; i < this.rangeAxes.size(); i++) { 3561 ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i); 3562 if (yAxis != null) { 3563 ValueAxis clonedAxis = (ValueAxis) yAxis.clone(); 3564 clone.setRangeAxis(i, clonedAxis); 3565 } 3566 } 3567 clone.rangeAxisLocations = (ObjectList) this.rangeAxisLocations.clone(); 3568 3569 clone.datasets = (ObjectList) this.datasets.clone(); 3570 for (int i = 0; i < clone.datasets.size(); i++) { 3571 CategoryDataset dataset = clone.getDataset(i); 3572 if (dataset != null) { 3573 dataset.addChangeListener(clone); 3574 } 3575 } 3576 clone.datasetToDomainAxisMap 3577 = (ObjectList) this.datasetToDomainAxisMap.clone(); 3578 clone.datasetToRangeAxisMap 3579 = (ObjectList) this.datasetToRangeAxisMap.clone(); 3580 clone.renderers = (ObjectList) this.renderers.clone(); 3581 if (this.fixedDomainAxisSpace != null) { 3582 clone.fixedDomainAxisSpace = (AxisSpace) ObjectUtilities.clone( 3583 this.fixedDomainAxisSpace); 3584 } 3585 if (this.fixedRangeAxisSpace != null) { 3586 clone.fixedRangeAxisSpace = (AxisSpace) ObjectUtilities.clone( 3587 this.fixedRangeAxisSpace); 3588 } 3589 3590 return clone; 3591 3592 } 3593 3594 /** 3595 * Provides serialization support. 3596 * 3597 * @param stream the output stream. 3598 * 3599 * @throws IOException if there is an I/O error. 3600 */ 3601 private void writeObject(ObjectOutputStream stream) throws IOException { 3602 stream.defaultWriteObject(); 3603 SerialUtilities.writeStroke(this.domainGridlineStroke, stream); 3604 SerialUtilities.writePaint(this.domainGridlinePaint, stream); 3605 SerialUtilities.writeStroke(this.rangeGridlineStroke, stream); 3606 SerialUtilities.writePaint(this.rangeGridlinePaint, stream); 3607 SerialUtilities.writeStroke(this.rangeCrosshairStroke, stream); 3608 SerialUtilities.writePaint(this.rangeCrosshairPaint, stream); 3609 } 3610 3611 /** 3612 * Provides serialization support. 3613 * 3614 * @param stream the input stream. 3615 * 3616 * @throws IOException if there is an I/O error. 3617 * @throws ClassNotFoundException if there is a classpath problem. 3618 */ 3619 private void readObject(ObjectInputStream stream) 3620 throws IOException, ClassNotFoundException { 3621 3622 stream.defaultReadObject(); 3623 this.domainGridlineStroke = SerialUtilities.readStroke(stream); 3624 this.domainGridlinePaint = SerialUtilities.readPaint(stream); 3625 this.rangeGridlineStroke = SerialUtilities.readStroke(stream); 3626 this.rangeGridlinePaint = SerialUtilities.readPaint(stream); 3627 this.rangeCrosshairStroke = SerialUtilities.readStroke(stream); 3628 this.rangeCrosshairPaint = SerialUtilities.readPaint(stream); 3629 3630 for (int i = 0; i < this.domainAxes.size(); i++) { 3631 CategoryAxis xAxis = (CategoryAxis) this.domainAxes.get(i); 3632 if (xAxis != null) { 3633 xAxis.setPlot(this); 3634 xAxis.addChangeListener(this); 3635 } 3636 } 3637 for (int i = 0; i < this.rangeAxes.size(); i++) { 3638 ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(i); 3639 if (yAxis != null) { 3640 yAxis.setPlot(this); 3641 yAxis.addChangeListener(this); 3642 } 3643 } 3644 int datasetCount = this.datasets.size(); 3645 for (int i = 0; i < datasetCount; i++) { 3646 Dataset dataset = (Dataset) this.datasets.get(i); 3647 if (dataset != null) { 3648 dataset.addChangeListener(this); 3649 } 3650 } 3651 int rendererCount = this.renderers.size(); 3652 for (int i = 0; i < rendererCount; i++) { 3653 CategoryItemRenderer renderer 3654 = (CategoryItemRenderer) this.renderers.get(i); 3655 if (renderer != null) { 3656 renderer.addChangeListener(this); 3657 } 3658 } 3659 3660 } 3661 3662 }