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