Source for org.jfree.chart.plot.XYPlot

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