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