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