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