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