Source for org.jfree.chart.plot.CategoryPlot

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