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