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