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