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