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