Package com.positive.charts.plot.xy

Source Code of com.positive.charts.plot.xy.XYPlot

package com.positive.charts.plot.xy;

import java.awt.Paint;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;

import com.positive.charts.axis.Axis;
import com.positive.charts.axis.AxisCollection;
import com.positive.charts.axis.AxisLocation;
import com.positive.charts.axis.AxisSpace;
import com.positive.charts.axis.AxisState;
import com.positive.charts.axis.ValueAxis;
import com.positive.charts.axis.ticks.ITick;
import com.positive.charts.axis.ticks.ValueTick;
import com.positive.charts.block.RectangleInsets;
import com.positive.charts.common.RectangleEdge;
import com.positive.charts.data.Range;
import com.positive.charts.data.util.DatasetUtilities;
import com.positive.charts.data.xy.XYDataset;
import com.positive.charts.event.ChartChangeEventType;
import com.positive.charts.event.DatasetChangeEvent;
import com.positive.charts.event.PlotChangeEvent;
import com.positive.charts.event.RendererChangeEvent;
import com.positive.charts.event.RendererChangeListener;
import com.positive.charts.legend.LegendItem;
import com.positive.charts.legend.LegendItemCollection;
import com.positive.charts.plot.CrosshairState;
import com.positive.charts.plot.DatasetRenderingOrder;
import com.positive.charts.plot.Plot;
import com.positive.charts.plot.PlotOrientation;
import com.positive.charts.plot.PlotRenderingInfo;
import com.positive.charts.plot.PlotState;
import com.positive.charts.plot.SeriesRenderingOrder;
import com.positive.charts.plot.ValueAxisPlot;
import com.positive.charts.plot.Zoomable;
import com.positive.charts.renderer.xy.XYItemRenderer;
import com.positive.charts.renderer.xy.XYItemRendererState;
import com.positive.charts.util.RectangleUtil;
import com.positive.charts.util.Stroke;

/**
* A general class for plotting data in the form of (x, y) pairs. This plot can
* use data from any class that implements the {@link XYDataset} interface.
* <p>
* <code>XYPlot</code> makes use of an {@link XYItemRenderer} to draw each point
* on the plot. By using different renderers, various chart types can be
* produced.
*/
public class XYPlot extends Plot implements ValueAxisPlot, Zoomable,
    RendererChangeListener {

  /** A color used to draw domain gridlines. */
  public static final String COLOR_DOMAIN_GRIDLINE = "XYPlot.domainGridlineColor"; //$NON-NLS-1$

  /** A stroke style used to draw domain gridlines. */
  public static final String STROKE_DOMAIN_GRIDLINE = "XYPlor.domainGridlineStroke"; //$NON-NLS-1$

  /** A color used to draw range gridlines. */
  public static final String COLOR_RANGE_GRIDLINE = "XYPlot.rangeGridlineColor"; //$NON-NLS-1$

  /** A stroke style used to draw range gridlines. */
  public static final String STROKE_RANGE_GRIDLINE = "XYPlot.rangeGridlineStroke"; //$NON-NLS-1$

  /** A color used to draw zero range gridline. */
  public static final String COLOR_ZERO_RANGE_GRIDLINE = "XYPlot.zeroRangeGridlineColor"; //$NON-NLS-1$

  /** A color used to draw zero range gridline. */
  public static final String STROKE_ZERO_RANGE_GRIDLINE = "XYPlot.zeroRangeGridlineStroke"; //$NON-NLS-1$

  // public static final Stroke DEFAULT_GRIDLINE_STROKE = new Stroke(1);

  /** The default grid line stroke. */
  public static final Stroke DEFAULT_GRIDLINE_STROKE = new Stroke(1,
      SWT.CAP_FLAT, SWT.JOIN_BEVEL, SWT.LINE_DASH, new int[] { 3, 3, 0 });

  /** The default grid line paint. */
  public static final Color DEFAULT_GRIDLINE_PAINT = new Color(Display
      .getDefault(), 222, 222, 222);

  /** The default crosshair visibility. */
  public static final boolean DEFAULT_CROSSHAIR_VISIBLE = false;

  /** The plot orientation. */
  private PlotOrientation orientation;

  /** The offset between the data area and the axes. */
  private RectangleInsets axisOffset = new RectangleInsets(1, 1, 1, 1);

  /** The domain axis / axes (used for the x-values). */
  private final List domainAxes;

  /** The domain axis locations. */
  private final List domainAxisLocations;

  /** The range axis (used for the y-values). */
  private final List rangeAxes;

  /** The range axis location. */
  private final List rangeAxisLocations;

  /** Storage for the datasets. */
  private final List datasets;

  /** Storage for the renderers. */
  private final List renderers;

  private Stroke domainGridlineStroke = DEFAULT_GRIDLINE_STROKE;

  private Stroke rangeGridlineStroke = DEFAULT_GRIDLINE_STROKE;

  private Color domainGridlinePaint = DEFAULT_GRIDLINE_PAINT;

  private Color rangeGridlinePaint = DEFAULT_GRIDLINE_PAINT;

  /**
   * Storage for keys that map datasets/renderers to domain axes. If the map
   * contains no entry for a dataset, it is assumed to map to the primary
   * domain axis (index = 0).
   */
  private final Map datasetToDomainAxisMap;

  /**
   * Storage for keys that map datasets/renderers to range axes. If the map
   * contains no entry for a dataset, it is assumed to map to the primary
   * domain axis (index = 0).
   */
  private final Map datasetToRangeAxisMap;

  /** The origin point for the quadrants (if drawn). */
  private transient Point quadrantOrigin = new Point(0, 0);

  /** A flag that controls whether the domain grid-lines are visible. */
  private boolean domainGridlinesVisible = true;

  /** A flag that controls whether the range grid-lines are visible. */
  private boolean rangeGridlinesVisible = true;

  /**
   * A flag that controls whether or not the zero baseline against the range
   * axis is visible.
   */
  private boolean rangeZeroBaselineVisible = false;

  /** A flag that controls whether or not a domain crosshair is drawn.. */
  private boolean domainCrosshairVisible;

  /** The domain crosshair value. */
  private double domainCrosshairValue;

  /**
   * A flag that controls whether or not the crosshair locks onto actual data
   * points.
   */
  private boolean domainCrosshairLockedOnData = true;

  /** A flag that controls whether or not a range crosshair is drawn.. */
  private boolean rangeCrosshairVisible;

  /** The range crosshair value. */
  private double rangeCrosshairValue;

  /**
   * A flag that controls whether or not the crosshair locks onto actual data
   * points.
   */
  private boolean rangeCrosshairLockedOnData = true;

  /** The fixed domain axis space. */
  private AxisSpace fixedDomainAxisSpace;

  /** The fixed range axis space. */
  private AxisSpace fixedRangeAxisSpace;

  /**
   * The order of the dataset rendering (REVERSE draws the primary dataset
   * last so that it appears to be on top).
   */
  private DatasetRenderingOrder datasetRenderingOrder = DatasetRenderingOrder.REVERSE;

  /**
   * The order of the series rendering (REVERSE draws the primary series last
   * so that it appears to be on top).
   */
  private SeriesRenderingOrder seriesRenderingOrder = SeriesRenderingOrder.REVERSE;

  /**
   * The weight for this plot (only relevant if this is a subplot in a
   * combined plot).
   */
  private int weight;

  /**
   * An optional collection of legend items that can be returned by the
   * getLegendItems() method.
   */
  private LegendItemCollection fixedLegendItems;

  /**
   * Creates a new <code>XYPlot</code> instance with no dataset, no axes and
   * no renderer. You should specify these items before using the plot.
   */
  public XYPlot() {
    this(null, null, null, null);
  }

  /**
   * Creates a new plot with the specified dataset, axes and renderer. Any of
   * the arguments can be <code>null</code>, but in that case you should take
   * care to specify the value before using the plot (otherwise a
   * <code>NullPointerException</code> may be thrown).
   *
   * @param dataset
   *            the dataset (<code>null</code> permitted).
   * @param domainAxis
   *            the domain axis (<code>null</code> permitted).
   * @param rangeAxis
   *            the range axis (<code>null</code> permitted).
   * @param renderer
   *            the renderer (<code>null</code> permitted).
   */
  public XYPlot(final XYDataset dataset, final ValueAxis domainAxis,
      final ValueAxis rangeAxis, final XYItemRenderer renderer) {

    super();

    this.orientation = PlotOrientation.VERTICAL;
    this.weight = 1; // only relevant when this is a subplot
    // this.axisOffset = RectangleInsets.ZERO_INSETS;

    // allocate storage for datasets, axes and renderers (all optional)
    this.domainAxes = new ArrayList();
    this.domainAxisLocations = new ArrayList();

    this.rangeAxes = new ArrayList();
    this.rangeAxisLocations = new ArrayList();

    this.datasets = new ArrayList();
    this.renderers = new ArrayList();

    this.datasetToDomainAxisMap = new TreeMap();
    this.datasetToRangeAxisMap = new TreeMap();

    this.datasets.add(0, dataset);
    if (dataset != null) {
      dataset.addChangeListener(this);
    }

    this.renderers.add(0, renderer);
    if (renderer != null) {
      renderer.setPlot(this);
      renderer.addChangeListener(this);
    }

    this.domainAxes.add(0, domainAxis);
    this.mapDatasetToDomainAxis(0, 0);
    if (domainAxis != null) {
      domainAxis.setPlot(this);
      domainAxis.addChangeListener(this);
    }
    this.domainAxisLocations.add(0, AxisLocation.BOTTOM_OR_LEFT);

    this.rangeAxes.add(0, rangeAxis);
    this.mapDatasetToRangeAxis(0, 0);
    if (rangeAxis != null) {
      rangeAxis.setPlot(this);
      rangeAxis.addChangeListener(this);
    }
    this.rangeAxisLocations.add(0, AxisLocation.BOTTOM_OR_LEFT);

    this.configureDomainAxes();
    this.configureRangeAxes();

    this.rangeZeroBaselineVisible = false;

    this.domainCrosshairVisible = false;
    this.domainCrosshairValue = 0.0;

    this.rangeCrosshairVisible = false;
    this.rangeCrosshairValue = 0.0;
  }

  /**
   * Calculates the space required for all the axes in the plot.
   *
   * @param gc
   *            the graphics device.
   * @param plotArea
   *            the plot area.
   *
   * @return The required space.
   */
  protected AxisSpace calculateAxisSpace(final GC gc, final Rectangle plotArea) {
    AxisSpace space = new AxisSpace();
    space = this.calculateDomainAxisSpace(gc, plotArea, space);
    space = this.calculateRangeAxisSpace(gc, plotArea, space);
    return space;
  }

  /**
   * Calculates the space required for the domain axis/axes.
   *
   * @param gc
   *            the graphics device.
   * @param plotArea
   *            the plot area.
   * @param space
   *            a carrier for the result (<code>null</code> permitted).
   *
   * @return The required space.
   */
  protected AxisSpace calculateDomainAxisSpace(final GC gc,
      final Rectangle plotArea, AxisSpace space) {

    if (space == null) {
      space = new AxisSpace();
    }

    // reserve some space for the domain axis...
    if (this.fixedDomainAxisSpace != null) {
      if (this.orientation == PlotOrientation.HORIZONTAL) {
        space.ensureAtLeast(this.fixedDomainAxisSpace.getLeft(),
            RectangleEdge.LEFT);
        space.ensureAtLeast(this.fixedDomainAxisSpace.getRight(),
            RectangleEdge.RIGHT);
      } else if (this.orientation == PlotOrientation.VERTICAL) {
        space.ensureAtLeast(this.fixedDomainAxisSpace.getTop(),
            RectangleEdge.TOP);
        space.ensureAtLeast(this.fixedDomainAxisSpace.getBottom(),
            RectangleEdge.BOTTOM);
      }
    } else {
      // reserve space for the domain axes...
      for (int i = 0; i < this.domainAxes.size(); i++) {
        final Axis axis = (Axis) this.domainAxes.get(i);
        if (axis != null) {
          final RectangleEdge edge = this.getDomainAxisEdge(i);
          space = axis.reserveSpace(gc, this, plotArea, edge, space);
        }
      }
    }

    return space;

  }

  /**
   * Calculates the space required for the range axis/axes.
   *
   * @param gc
   *            the graphics device.
   * @param plotArea
   *            the plot area.
   * @param space
   *            a carrier for the result (<code>null</code> permitted).
   *
   * @return The required space.
   */
  protected AxisSpace calculateRangeAxisSpace(final GC gc,
      final Rectangle plotArea, AxisSpace space) {

    if (space == null) {
      space = new AxisSpace();
    }

    // reserve some space for the range axis...
    if (this.fixedRangeAxisSpace != null) {
      if (this.orientation == PlotOrientation.HORIZONTAL) {
        space.ensureAtLeast(this.fixedRangeAxisSpace.getTop(),
            RectangleEdge.TOP);
        space.ensureAtLeast(this.fixedRangeAxisSpace.getBottom(),
            RectangleEdge.BOTTOM);
      } else if (this.orientation == PlotOrientation.VERTICAL) {
        space.ensureAtLeast(this.fixedRangeAxisSpace.getLeft(),
            RectangleEdge.LEFT);
        space.ensureAtLeast(this.fixedRangeAxisSpace.getRight(),
            RectangleEdge.RIGHT);
      }
    } else {
      // reserve space for the range axes...
      for (int i = 0; i < this.rangeAxes.size(); i++) {
        final Axis axis = (Axis) this.rangeAxes.get(i);
        if (axis != null) {
          final RectangleEdge edge = this.getRangeAxisEdge(i);
          space = axis.reserveSpace(gc, this, plotArea, edge, space);
        }
      }
    }
    return space;

  }

  /**
   * Clears the domain axes from the plot and sends a {@link PlotChangeEvent}
   * to all registered listeners.
   */
  public void clearDomainAxes() {
    for (int i = 0; i < this.domainAxes.size(); i++) {
      final ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
      if (axis != null) {
        axis.removeChangeListener(this);
      }
    }
    this.domainAxes.clear();
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Clears the range axes from the plot and sends a {@link PlotChangeEvent}
   * to all registered listeners.
   */
  public void clearRangeAxes() {
    for (int i = 0; i < this.rangeAxes.size(); i++) {
      final ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
      if (axis != null) {
        axis.removeChangeListener(this);
      }
    }
    this.rangeAxes.clear();
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Configures the domain axes.
   */
  public void configureDomainAxes() {
    for (int i = 0; i < this.domainAxes.size(); i++) {
      final ValueAxis axis = (ValueAxis) this.domainAxes.get(i);
      if (axis != null) {
        axis.configure();
      }
    }
  }

  /**
   * Configures the range axes.
   */
  public void configureRangeAxes() {
    for (int i = 0; i < this.rangeAxes.size(); i++) {
      final ValueAxis axis = (ValueAxis) this.rangeAxes.get(i);
      if (axis != null) {
        axis.configure();
      }
    }
  }

  /**
   * Receives notification of a change to the plot's dataset.
   * <P>
   * The axis ranges are updated if necessary.
   *
   * @param event
   *            information about the event (not used here).
   */
  public void datasetChanged(final DatasetChangeEvent event) {
    this.configureDomainAxes();
    this.configureRangeAxes();
    if (this.getParent() != null) {
      this.getParent().datasetChanged(event);
    } else {
      final PlotChangeEvent e = new PlotChangeEvent(this);
      e.setType(ChartChangeEventType.DATASET_UPDATED);
      this.notifyListeners(e);
    }
  }

  /**
   * Draws the plot within the specified area on a graphics device.
   *
   * @param gc
   *            the graphics device.
   * @param area
   *            the plot area (in Java2D space).
   * @param anchor
   *            an anchor point in Java2D space (<code>null</code> permitted).
   * @param parentState
   *            the state from the parent plot, if there is one (
   *            <code>null</code> permitted).
   * @param info
   *            collects chart drawing information (<code>null</code>
   *            permitted).
   */
  public void draw(final GC gc, final Rectangle area, Point anchor,
      final PlotState parentState, final PlotRenderingInfo info) {

    // if the plot area is too small, just return...
    final boolean b1 = (area.width <= MINIMUM_WIDTH_TO_DRAW);
    final boolean b2 = (area.height <= MINIMUM_HEIGHT_TO_DRAW);
    if (b1 || b2) {
      return;
    }
    gc.setAdvanced(true);

    gc.setAntialias(SWT.ON);
    // record the plot area...
    if (info != null) {
      info.setPlotArea(area);
    }

    // adjust the drawing area for the plot insets (if any)...
    final RectangleInsets insets = this.getInsets();
    insets.trim(area);

    final AxisSpace space = this.calculateAxisSpace(gc, area);
    final Rectangle dataArea = space.shrink(area, null);
    this.axisOffset.trim(dataArea);

    if (info != null) {
      info.setDataArea(dataArea);
    }

    // draw the plot background and axes...
    this.drawBackground(gc, dataArea);
    final Map axisStateMap = this.drawAxes(gc, area, dataArea, info);
    final PlotOrientation orient = this.getOrientation();
    // the anchor point is typically the point where the mouse last
    // clicked - the crosshairs will be driven off this point...
    if ((anchor != null) && !dataArea.contains(anchor)) {
      anchor = null;
    }
    final CrosshairState crosshairState = new CrosshairState();
    crosshairState.setCrosshairDistance(Double.POSITIVE_INFINITY);
    crosshairState.setAnchor(anchor);

    crosshairState.setAnchorX(Double.NaN);
    crosshairState.setAnchorY(Double.NaN);
    if (anchor != null) {
      final ValueAxis domainAxis = this.getDomainAxis();
      if (domainAxis != null) {
        double x;
        if (orient == PlotOrientation.VERTICAL) {
          x = domainAxis.java2DToValue(anchor.x, dataArea, this
              .getDomainAxisEdge());
        } else {
          x = domainAxis.java2DToValue(anchor.y, dataArea, this
              .getDomainAxisEdge());
        }
        crosshairState.setAnchorX(x);
      }
      final ValueAxis rangeAxis = this.getRangeAxis();
      if (rangeAxis != null) {
        double y;
        if (orient == PlotOrientation.VERTICAL) {
          y = rangeAxis.java2DToValue(anchor.y, dataArea, this
              .getRangeAxisEdge());
        } else {
          y = rangeAxis.java2DToValue(anchor.x, dataArea, this
              .getRangeAxisEdge());
        }
        crosshairState.setAnchorY(y);
      }
    }
    crosshairState.setCrosshairX(this.getDomainCrosshairValue());
    crosshairState.setCrosshairY(this.getRangeCrosshairValue());
    gc.setClipping(dataArea);

    AxisState domainAxisState = (AxisState) axisStateMap.get(this
        .getDomainAxis());
    if (domainAxisState == null) {
      if (parentState != null) {
        domainAxisState = (AxisState) parentState.getSharedAxisStates()
            .get(this.getDomainAxis());
      }
    }

    AxisState rangeAxisState = (AxisState) axisStateMap.get(this
        .getRangeAxis());
    if (rangeAxisState == null) {
      if (parentState != null) {
        rangeAxisState = (AxisState) parentState.getSharedAxisStates()
            .get(this.getRangeAxis());
      }
    }
    if (domainAxisState != null) {
      this.drawDomainGridlines(gc, dataArea, domainAxisState.getTicks());
    }
    if (rangeAxisState != null) {
      this.drawRangeGridlines(gc, dataArea, rangeAxisState.getTicks());
      this.drawZeroRangeBaseline(gc, dataArea);
    }

    // now draw annotations and render data items...
    boolean foundData = false;
    final DatasetRenderingOrder order = this.getDatasetRenderingOrder();
    if (order == DatasetRenderingOrder.FORWARD) {

      // render data items...
      for (int i = 0; i < this.getDatasetCount(); i++) {
        foundData = this.render(gc, dataArea, i, info, crosshairState)
            || foundData;
      }

    } else if (order == DatasetRenderingOrder.REVERSE) {

      for (int i = this.getDatasetCount() - 1; i >= 0; i--) {
        foundData = this.render(gc, dataArea, i, info, crosshairState)
            || foundData;
      }

    }

    this.drawOutline(gc, dataArea);
  }

  /**
   * A utility method for drawing the axes.
   *
   * @param gc
   *            the graphics device (<code>null</code> not permitted).
   * @param plotArea
   *            the plot area (<code>null</code> not permitted).
   * @param dataArea
   *            the data area (<code>null</code> not permitted).
   * @param plotState
   *            collects information about the plot (<code>null</code>
   *            permitted).
   *
   * @return A map containing the state for each axis drawn.
   */
  protected Map drawAxes(final GC gc, final Rectangle plotArea,
      final Rectangle dataArea, final PlotRenderingInfo plotState) {

    final AxisCollection axisCollection = new AxisCollection();

    // add domain axes to lists...
    for (int index = 0; index < this.domainAxes.size(); index++) {
      final ValueAxis axis = (ValueAxis) this.domainAxes.get(index);
      if (axis != null) {
        axisCollection.add(axis, this.getDomainAxisEdge(index));
      }
    }

    // add range axes to lists...
    for (int index = 0; index < this.rangeAxes.size(); index++) {
      final ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
      if (yAxis != null) {
        axisCollection.add(yAxis, this.getRangeAxisEdge(index));
      }
    }

    final Map axisStateMap = new HashMap();

    // draw the top axes
    double cursor = RectangleUtil.getMinY(dataArea)
        - this.axisOffset.calculateTopOutset(dataArea.height);
    Iterator iterator = axisCollection.getAxesAtTop().iterator();
    while (iterator.hasNext()) {
      final ValueAxis axis = (ValueAxis) iterator.next();
      final AxisState info = axis.draw(gc, cursor, plotArea, dataArea,
          RectangleEdge.TOP, plotState);
      cursor = info.getCursor();
      axisStateMap.put(axis, info);
    }

    // draw the bottom axes
    cursor = RectangleUtil.getMaxY(dataArea)
        + this.axisOffset.calculateBottomOutset(dataArea.height);
    iterator = axisCollection.getAxesAtBottom().iterator();
    while (iterator.hasNext()) {
      final ValueAxis axis = (ValueAxis) iterator.next();
      final AxisState info = axis.draw(gc, cursor, plotArea, dataArea,
          RectangleEdge.BOTTOM, plotState);
      cursor = info.getCursor();
      axisStateMap.put(axis, info);
    }

    // draw the left axes
    cursor = RectangleUtil.getMinX(dataArea)
        - this.axisOffset.calculateLeftOutset(dataArea.width);
    iterator = axisCollection.getAxesAtLeft().iterator();
    while (iterator.hasNext()) {
      final ValueAxis axis = (ValueAxis) iterator.next();
      final AxisState info = axis.draw(gc, cursor, plotArea, dataArea,
          RectangleEdge.LEFT, plotState);
      cursor = info.getCursor();
      axisStateMap.put(axis, info);
    }

    // draw the right axes
    cursor = RectangleUtil.getMaxX(dataArea)
        + this.axisOffset.calculateRightOutset(dataArea.width);
    iterator = axisCollection.getAxesAtRight().iterator();
    while (iterator.hasNext()) {
      final ValueAxis axis = (ValueAxis) iterator.next();
      final AxisState info = axis.draw(gc, cursor, plotArea, dataArea,
          RectangleEdge.RIGHT, plotState);
      cursor = info.getCursor();
      axisStateMap.put(axis, info);
    }

    return axisStateMap;
  }

  /**
   * Draws the background for the plot.
   *
   * @param gc
   *            the graphics device.
   * @param area
   *            the area.
   */
  public void drawBackground(final GC gc, final Rectangle area) {
    this.fillBackground(gc, area);
  }

  /**
   * Draws the gridlines for the plot, if they are visible.
   *
   * @param gc
   *            the graphics device.
   * @param dataArea
   *            the data area.
   * @param ticks
   *            the ticks.
   */
  protected void drawDomainGridlines(final GC gc, final Rectangle dataArea,
      final List ticks) {

    // no renderer, no gridlines...
    if (this.getRenderer() == null) {
      return;
    }

    // draw the domain grid lines, if any...
    if (this.isDomainGridlinesVisible()) {
      this.applyForegroundColor(gc, COLOR_DOMAIN_GRIDLINE);
      this.applyStroke(gc, STROKE_DOMAIN_GRIDLINE);

      for (final Iterator iter = ticks.iterator(); iter.hasNext();) {
        final ITick tick = (ITick) iter.next();
        if (tick instanceof ValueTick) {
          this.getRenderer().drawDomainGridLine(gc, this,
              this.getDomainAxis(), dataArea,
              ((ValueTick) tick).getValue());
        }
      }
    }
  }

  /**
   * Utility method for drawing a horizontal line across the data area of the
   * plot.
   *
   * @param gc
   *            the graphics device.
   * @param dataArea
   *            the data area.
   * @param value
   *            the coordinate, where to draw the line.
   * @param stroke
   *            the stroke to use.
   * @param paint
   *            the paint to use.
   */
  protected void drawHorizontalLine(final GC gc, final Rectangle dataArea,
      final double value) {

    ValueAxis axis = this.getRangeAxis();
    if (this.getOrientation() == PlotOrientation.HORIZONTAL) {
      axis = this.getDomainAxis();
    }
    if (axis.getRange().contains(value)) {
      final int yy = (int) axis.valueToJava2D(value, dataArea,
          RectangleEdge.LEFT);
      gc.drawLine(RectangleUtil.getMinX(dataArea), yy, RectangleUtil
          .getMaxX(dataArea), yy);
    }

  }

  /**
   * Draws the gridlines for the plot's primary range axis, if they are
   * visible.
   *
   * @param gc
   *            the graphics device.
   * @param area
   *            the data area.
   * @param ticks
   *            the ticks.
   */
  protected void drawRangeGridlines(final GC gc, final Rectangle area,
      final List ticks) {

    // draw the range grid lines, if any...
    if (this.isRangeGridlinesVisible()) {
      final ValueAxis axis = this.getRangeAxis();
      if (axis != null) {
        this.applyForegroundColor(gc, COLOR_RANGE_GRIDLINE);
        this.applyStroke(gc, STROKE_RANGE_GRIDLINE);

        for (final Iterator iter = ticks.iterator(); iter.hasNext();) {
          final ITick tick = (ITick) iter.next();
          if ((tick instanceof ValueTick) == false) {
            continue;
          }
          if ((((ValueTick) tick).getValue() != 0.0)
              || !this.isRangeZeroBaselineVisible()) {
            this.getRenderer().drawRangeLine(gc, this,
                this.getRangeAxis(), area,
                ((ValueTick) tick).getValue());
          }
        }
      }
    }
  }

  /**
   * Utility method for drawing a vertical line on the data area of the plot.
   *
   * @param gc
   *            the graphics device.
   * @param dataArea
   *            the data area.
   * @param value
   *            the coordinate, where to draw the line.
   * @param stroke
   *            the stroke to use.
   * @param paint
   *            the paint to use.
   */
  protected void drawVerticalLine(final GC gc, final Rectangle dataArea,
      final double value) {

    ValueAxis axis = this.getDomainAxis();
    if (this.getOrientation() == PlotOrientation.HORIZONTAL) {
      axis = this.getRangeAxis();
    }
    if (axis.getRange().contains(value)) {
      final int xx = (int) axis.valueToJava2D(value, dataArea,
          RectangleEdge.BOTTOM);
      gc.drawLine(xx, RectangleUtil.getMinY(dataArea), xx, RectangleUtil
          .getMaxY(dataArea));
    }

  }

  /**
   * Draws a base line across the chart at value zero on the range axis.
   *
   * @param gc
   *            the graphics device.
   * @param area
   *            the data area.
   */
  protected void drawZeroRangeBaseline(final GC gc, final Rectangle area) {
    if (this.isRangeZeroBaselineVisible()) {
      final Color color = this.getColor(COLOR_ZERO_RANGE_GRIDLINE);
      final Stroke stroke = this.getStroke(STROKE_ZERO_RANGE_GRIDLINE);
      this.getRenderer().drawRangeLine(gc, this, this.getRangeAxis(),
          area, 0.0, color, stroke);
    }
  }

  /**
   * Returns the axis offset.
   *
   * @return The axis offset (never <code>null</code>).
   */
  public RectangleInsets getAxisOffset() {
    return this.axisOffset;
  }

  /**
   * Returns the range for the specified axis.
   *
   * @param axis
   *            the axis.
   *
   * @return The range.
   */
  public Range getDataRange(final ValueAxis axis) {
    Range result = null;
    final List mappedDatasets = new ArrayList();
    boolean isDomainAxis = true;

    // is it a domain axis?
    final int domainIndex = this.getDomainAxisIndex(axis);
    if (domainIndex >= 0) {
      isDomainAxis = true;
      mappedDatasets.addAll(this
          .getDatasetsMappedToDomainAxis(new Integer(domainIndex)));
    }

    // or is it a range axis?
    final int rangeIndex = this.getRangeAxisIndex(axis);
    if (rangeIndex >= 0) {
      isDomainAxis = false;
      mappedDatasets.addAll(this
          .getDatasetsMappedToRangeAxis(new Integer(rangeIndex)));
    }

    // iterate through the datasets that map to the axis and get the union
    // of the ranges.
    final Iterator iterator = mappedDatasets.iterator();
    while (iterator.hasNext()) {
      final XYDataset d = (XYDataset) iterator.next();
      if (d != null) {
        final XYItemRenderer r = this.getRendererForDataset(d);
        if (isDomainAxis) {
          if (r != null) {
            result = Range.combine(result, r.findDomainBounds(d));
          } else {
            result = Range.combine(result, DatasetUtilities
                .findDomainBounds(d));
          }
        } else {
          if (r != null) {
            result = Range.combine(result, r.findRangeBounds(d));
          } else {
            result = Range.combine(result, DatasetUtilities
                .findRangeBounds(d));
          }
        }
      }
    }
    return result;

  }

  /**
   * Returns the primary dataset for the plot.
   *
   * @return The primary dataset (possibly <code>null</code>).
   */
  public XYDataset getDataset() {
    return this.getDataset(0);
  }

  /**
   * Returns a dataset.
   *
   * @param index
   *            the dataset index.
   *
   * @return The dataset (possibly <code>null</code>).
   */
  public XYDataset getDataset(final int index) {
    XYDataset result = null;
    if (this.datasets.size() > index) {
      result = (XYDataset) this.datasets.get(index);
    }
    return result;
  }

  /**
   * Returns the number of datasets.
   *
   * @return The number of datasets.
   */
  public int getDatasetCount() {
    return this.datasets.size();
  }

  /**
   * Returns the dataset rendering order.
   *
   * @return The order (never <code>null</code>).
   */
  public DatasetRenderingOrder getDatasetRenderingOrder() {
    return this.datasetRenderingOrder;
  }

  /**
   * A utility method that returns a list of datasets that are mapped to a
   * particular axis.
   *
   * @param axisIndex
   *            the axis index (<code>null</code> not permitted).
   *
   * @return A list of datasets.
   */
  private List getDatasetsMappedToDomainAxis(final Integer axisIndex) {
    if (axisIndex == null) {
      throw new IllegalArgumentException("Null 'axisIndex' argument.");
    }
    final List result = new ArrayList();
    for (int i = 0; i < this.datasets.size(); i++) {
      final Integer mappedAxis = (Integer) this.datasetToDomainAxisMap
          .get(new Integer(i));
      if (mappedAxis == null) {
        if (axisIndex.equals(ZERO)) {
          result.add(this.datasets.get(i));
        }
      } else {
        if (mappedAxis.equals(axisIndex)) {
          result.add(this.datasets.get(i));
        }
      }
    }
    return result;
  }

  /**
   * A utility method that returns a list of datasets that are mapped to a
   * particular axis.
   *
   * @param axisIndex
   *            the axis index (<code>null</code> not permitted).
   *
   * @return A list of datasets.
   */
  private List getDatasetsMappedToRangeAxis(final Integer axisIndex) {
    if (axisIndex == null) {
      throw new IllegalArgumentException("Null 'axisIndex' argument.");
    }
    final List result = new ArrayList();
    for (int i = 0; i < this.datasets.size(); i++) {
      final Integer mappedAxis = (Integer) this.datasetToRangeAxisMap
          .get(new Integer(i));
      if (mappedAxis == null) {
        if (axisIndex.equals(ZERO)) {
          result.add(this.datasets.get(i));
        }
      } else {
        if (mappedAxis.equals(axisIndex)) {
          result.add(this.datasets.get(i));
        }
      }
    }
    return result;
  }

  /**
   * Returns the domain axis for the plot. If the domain axis for this plot is
   * null, then the method will return the parent plot's domain axis (if there
   * is a parent plot).
   *
   * @return The domain axis.
   */
  public ValueAxis getDomainAxis() {
    return this.getDomainAxis(0);
  }

  /**
   * Returns a domain axis.
   *
   * @param index
   *            the axis index.
   *
   * @return The axis (<code>null</code> possible).
   */
  public ValueAxis getDomainAxis(final int index) {
    ValueAxis result = null;
    if (index < this.domainAxes.size()) {
      result = (ValueAxis) this.domainAxes.get(index);
    }
    if (result == null) {
      final Plot parent = this.getParent();
      if (parent instanceof XYPlot) {
        final XYPlot xy = (XYPlot) parent;
        result = xy.getDomainAxis(index);
      }
    }
    return result;
  }

  /**
   * Returns the number of domain axes.
   *
   * @return The axis count.
   */
  public int getDomainAxisCount() {
    return this.domainAxes.size();
  }

  /**
   * Returns the edge for the primary domain axis (taking into account the
   * plot's orientation).
   *
   * @return The edge.
   */
  public RectangleEdge getDomainAxisEdge() {
    return Plot.resolveDomainAxisLocation(this.getDomainAxisLocation(),
        this.orientation);
  }

  /**
   * Returns the edge for a domain axis.
   *
   * @param index
   *            the axis index.
   *
   * @return The edge.
   */
  public RectangleEdge getDomainAxisEdge(final int index) {
    final AxisLocation location = this.getDomainAxisLocation(index);
    RectangleEdge result = Plot.resolveDomainAxisLocation(location,
        this.orientation);
    if (result == null) {
      result = this.getDomainAxisEdge().opposite();
    }
    return result;
  }

  /**
   * Returns the domain axis for a dataset.
   *
   * @param index
   *            the dataset index.
   *
   * @return The axis.
   */
  public ValueAxis getDomainAxisForDataset(final int index) {

    if ((index < 0) || (index >= this.getDatasetCount())) {
      throw new IllegalArgumentException("Index 'index' out of bounds.");
    }

    ValueAxis valueAxis = null;
    final Integer axisIndex = (Integer) this.datasetToDomainAxisMap
        .get(new Integer(index));
    if (axisIndex != null) {
      valueAxis = this.getDomainAxis(axisIndex.intValue());
    } else {
      valueAxis = this.getDomainAxis(0);
    }
    return valueAxis;

  }

  /**
   * Returns the index of the given domain axis.
   *
   * @param axis
   *            the axis.
   *
   * @return The axis index.
   */
  public int getDomainAxisIndex(final ValueAxis axis) {
    int result = this.domainAxes.indexOf(axis);
    if (result < 0) {
      // try the parent plot
      final Plot parent = this.getParent();
      if (parent instanceof XYPlot) {
        final XYPlot p = (XYPlot) parent;
        result = p.getDomainAxisIndex(axis);
      }
    }
    return result;
  }

  /**
   * Returns the location of the primary domain axis.
   *
   * @return The location (never <code>null</code>).
   */
  public AxisLocation getDomainAxisLocation() {
    return (AxisLocation) this.domainAxisLocations.get(0);
  }

  /**
   * Returns the location for a domain axis. If this hasn't been set
   * explicitly, the method returns the location that is opposite to the
   * primary domain axis location.
   *
   * @param index
   *            the axis index.
   *
   * @return The location (never <code>null</code>).
   */
  public AxisLocation getDomainAxisLocation(final int index) {
    AxisLocation result = null;
    if (index < this.domainAxisLocations.size()) {
      result = (AxisLocation) this.domainAxisLocations.get(index);
    }
    if (result == null) {
      result = this.getDomainAxisLocation().opposite();
    }
    return result;
  }

  /**
   * Returns the domain crosshair value.
   *
   * @return The value.
   *
   * @see #setDomainCrosshairValue(double)
   */
  public double getDomainCrosshairValue() {
    return this.domainCrosshairValue;
  }

  /**
   * Returns the paint for the grid lines (if any) plotted against the domain
   * axis.
   *
   * @return The paint (never <code>null</code>).
   *
   * @see #setDomainGridlinePaint(Paint)
   */
  public Color getDomainGridlinePaint() {
    return this.domainGridlinePaint;
  }

  /**
   * Returns the stroke for the grid-lines (if any) plotted against the domain
   * axis.
   *
   * @return The stroke (never <code>null</code>).
   *
   * @see #setDomainGridlineStroke(Stroke)
   */
  public Stroke getDomainGridlineStroke() {
    return this.domainGridlineStroke;
  }

  /**
   * Returns the fixed domain axis space.
   *
   * @return The fixed domain axis space (possibly <code>null</code>).
   */
  public AxisSpace getFixedDomainAxisSpace() {
    return this.fixedDomainAxisSpace;
  }

  /**
   * Returns the fixed legend items, if any.
   *
   * @return The legend items (possibly <code>null</code>).
   */
  public LegendItemCollection getFixedLegendItems() {
    return this.fixedLegendItems;
  }

  /**
   * Returns the fixed range axis space.
   *
   * @return The fixed range axis space.
   */
  public AxisSpace getFixedRangeAxisSpace() {
    return this.fixedRangeAxisSpace;
  }

  /**
   * Returns the index of the specified renderer, or <code>-1</code> if the
   * renderer is not assigned to this plot.
   *
   * @param renderer
   *            the renderer (<code>null</code> permitted).
   *
   * @return The renderer index.
   */
  public int getIndexOf(final XYItemRenderer renderer) {
    return this.renderers.indexOf(renderer);
  }

  /**
   * Returns the legend items for the plot. Each legend item is generated by
   * the plot's renderer, since the renderer is responsible for the visual
   * representation of the data.
   *
   * @return The legend items.
   */
  public LegendItemCollection getLegendItems() {
    if (this.fixedLegendItems != null) {
      return this.fixedLegendItems;
    }
    final LegendItemCollection result = new LegendItemCollection();
    final int count = this.datasets.size();
    for (int datasetIndex = 0; datasetIndex < count; datasetIndex++) {
      final XYDataset dataset = this.getDataset(datasetIndex);
      if (dataset != null) {
        XYItemRenderer renderer = this.getRenderer(datasetIndex);
        if (renderer == null) {
          renderer = this.getRenderer(0);
        }
        if (renderer != null) {
          final int seriesCount = dataset.getSeriesCount();
          for (int i = 0; i < seriesCount; i++) {
            if (renderer.isSeriesVisible(i)
                && renderer.isSeriesVisibleInLegend(i)) {
              final LegendItem item = renderer.getLegendItem(
                  datasetIndex, i);
              if (item != null) {
                result.add(item);
              }
            }
          }
        }
      }
    }
    return result;
  }

  /**
   * Returns the orientation of the plot.
   *
   * @return The orientation of the plot.
   */
  public PlotOrientation getOrientation() {
    return this.orientation;
  }

  /**
   * Returns the plot type as a string.
   *
   * @return A short string describing the type of plot.
   */
  public String getPlotType() {
    return "XY Plot";
  }

  /**
   * Returns the origin for the quadrants that can be displayed on the plot.
   * This defaults to (0, 0).
   *
   * @return The origin point (never <code>null</code>).
   */
  public Point getQuadrantOrigin() {
    return this.quadrantOrigin;
  }

  /**
   * Returns the range axis for the plot. If the range axis for this plot is
   * null, then the method will return the parent plot's range axis (if there
   * is a parent plot).
   *
   * @return The range axis.
   */
  public ValueAxis getRangeAxis() {
    return this.getRangeAxis(0);
  }

  /**
   * Returns a range axis.
   *
   * @param index
   *            the axis index.
   *
   * @return The axis (<code>null</code> possible).
   */
  public ValueAxis getRangeAxis(final int index) {
    ValueAxis result = null;
    if (index < this.rangeAxes.size()) {
      result = (ValueAxis) this.rangeAxes.get(index);
    }
    if (result == null) {
      final Plot parent = this.getParent();
      if (parent instanceof XYPlot) {
        final XYPlot xy = (XYPlot) parent;
        result = xy.getRangeAxis(index);
      }
    }
    return result;
  }

  /**
   * Returns the number of range axes.
   *
   * @return The axis count.
   */
  public int getRangeAxisCount() {
    return this.rangeAxes.size();
  }

  /**
   * Returns the edge for the primary range axis.
   *
   * @return The range axis edge.
   */
  public RectangleEdge getRangeAxisEdge() {
    return Plot.resolveRangeAxisLocation(this.getRangeAxisLocation(),
        this.orientation);
  }

  /**
   * Returns the edge for a range axis.
   *
   * @param index
   *            the axis index.
   *
   * @return The edge.
   */
  public RectangleEdge getRangeAxisEdge(final int index) {
    final AxisLocation location = this.getRangeAxisLocation(index);
    RectangleEdge result = Plot.resolveRangeAxisLocation(location,
        this.orientation);
    if (result == null) {
      result = this.getRangeAxisEdge().opposite();
    }
    return result;
  }

  /**
   * Returns the range axis for a dataset.
   *
   * @param index
   *            the dataset index.
   *
   * @return The axis.
   */
  public ValueAxis getRangeAxisForDataset(final int index) {

    if ((index < 0) || (index >= this.getDatasetCount())) {
      throw new IllegalArgumentException("Index 'index' out of bounds.");
    }

    ValueAxis valueAxis = null;
    final Integer axisIndex = (Integer) this.datasetToRangeAxisMap
        .get(new Integer(index));
    if (axisIndex != null) {
      valueAxis = this.getRangeAxis(axisIndex.intValue());
    } else {
      valueAxis = this.getRangeAxis(0);
    }
    return valueAxis;

  }

  /**
   * Returns the index of the given range axis.
   *
   * @param axis
   *            the axis.
   *
   * @return The axis index.
   */
  public int getRangeAxisIndex(final ValueAxis axis) {
    int result = this.rangeAxes.indexOf(axis);
    if (result < 0) {
      // try the parent plot
      final Plot parent = this.getParent();
      if (parent instanceof XYPlot) {
        final XYPlot p = (XYPlot) parent;
        result = p.getRangeAxisIndex(axis);
      }
    }
    return result;
  }

  /**
   * Returns the location of the primary range axis.
   *
   * @return The location (never <code>null</code>).
   */
  public AxisLocation getRangeAxisLocation() {
    return (AxisLocation) this.rangeAxisLocations.get(0);
  }

  /**
   * Returns the location for a range axis. If this hasn't been set
   * explicitly, the method returns the location that is opposite to the
   * primary range axis location.
   *
   * @param index
   *            the axis index.
   *
   * @return The location (never <code>null</code>).
   */
  public AxisLocation getRangeAxisLocation(final int index) {
    AxisLocation result = null;
    if (index < this.rangeAxisLocations.size()) {
      result = (AxisLocation) this.rangeAxisLocations.get(index);
    }
    if (result == null) {
      result = this.getRangeAxisLocation().opposite();
    }
    return result;
  }

  /**
   * Returns the range crosshair value.
   *
   * @return The value.
   *
   * @see #setRangeCrosshairValue(double)
   */
  public double getRangeCrosshairValue() {
    return this.rangeCrosshairValue;
  }

  /**
   * Returns the paint for the grid lines (if any) plotted against the range
   * axis.
   *
   * @return The paint (never <code>null</code>).
   *
   * @see #setRangeGridlinePaint(Paint)
   */
  public Color getRangeGridlinePaint() {
    return this.rangeGridlinePaint;
  }

  /**
   * Returns the stroke for the grid lines (if any) plotted against the range
   * axis.
   *
   * @return The stroke (never <code>null</code>).
   *
   * @see #setRangeGridlineStroke(Stroke)
   */
  public Stroke getRangeGridlineStroke() {
    return this.rangeGridlineStroke;
  }

  /**
   * Returns the renderer for the primary dataset.
   *
   * @return The item renderer (possibly <code>null</code>).
   */
  public XYItemRenderer getRenderer() {
    return this.getRenderer(0);
  }

  /**
   * Returns the renderer for a dataset, or <code>null</code>.
   *
   * @param index
   *            the renderer index.
   *
   * @return The renderer (possibly <code>null</code>).
   */
  public XYItemRenderer getRenderer(final int index) {
    if ((index >= 0) && (index < this.renderers.size())) {
      return (XYItemRenderer) this.renderers.get(index);
    }
    return null;
  }

  /**
   * Returns the renderer for the specified dataset. The code first determines
   * the index of the dataset, then checks if there is a renderer with the
   * same index (if not, the method returns renderer(0).
   *
   * @param dataset
   *            the dataset (<code>null</code> permitted).
   *
   * @return The renderer (possibly <code>null</code>).
   */
  public XYItemRenderer getRendererForDataset(final XYDataset dataset) {
    XYItemRenderer result = null;
    for (int i = 0; i < this.datasets.size(); i++) {
      if (this.datasets.get(i) == dataset) {
        if (i < this.renderers.size()) {
          result = (XYItemRenderer) this.renderers.get(i);
        }
        if (result == null) {
          result = this.getRenderer();
        }
        break;
      }
    }
    return result;
  }

  /**
   * Returns the number of series in the primary dataset for this plot. If the
   * dataset is <code>null</code>, the method returns 0.
   *
   * @return The series count.
   */
  public int getSeriesCount() {
    int result = 0;
    final XYDataset dataset = this.getDataset();
    if (dataset != null) {
      result = dataset.getSeriesCount();
    }
    return result;
  }

  /**
   * Returns the series rendering order.
   *
   * @return the order (never <code>null</code>).
   */
  public SeriesRenderingOrder getSeriesRenderingOrder() {
    return this.seriesRenderingOrder;
  }

  /**
   * Returns the weight for this plot when it is used as a subplot within a
   * combined plot.
   *
   * @return The weight.
   */
  public int getWeight() {
    return this.weight;
  }

  /**
   * Handles a 'click' on the plot by updating the anchor values...
   *
   * @param x
   *            the x-coordinate, where the click occurred, in Java2D space.
   * @param y
   *            the y-coordinate, where the click occurred, in Java2D space.
   * @param info
   *            object containing information about the plot dimensions.
   */
  public void handleClick(final int x, final int y,
      final PlotRenderingInfo info) {

    final Rectangle dataArea = info.getDataArea();
    if (dataArea.contains(x, y)) {
      // set the anchor value for the horizontal axis...
      final ValueAxis da = this.getDomainAxis();
      if (da != null) {
        final double hvalue = da.java2DToValue(x, info.getDataArea(),
            this.getDomainAxisEdge());
        this.setDomainCrosshairValue(hvalue);
      }

      // set the anchor value for the vertical axis...
      final ValueAxis ra = this.getRangeAxis();
      if (ra != null) {
        final double vvalue = ra.java2DToValue(y, info.getDataArea(),
            this.getRangeAxisEdge());
        this.setRangeCrosshairValue(vvalue);
      }
    }
  }

  /**
   * Returns the index of the specified dataset, or <code>-1</code> if the
   * dataset does not belong to the plot.
   *
   * @param dataset
   *            the dataset (<code>null</code> not permitted).
   *
   * @return The index.
   */
  public int indexOf(final XYDataset dataset) {
    int result = -1;
    for (int i = 0; i < this.datasets.size(); i++) {
      if (dataset == this.datasets.get(i)) {
        result = i;
        break;
      }
    }
    return result;
  }

  /**
   * Returns a flag indicating whether or not the crosshair should "lock-on"
   * to actual data values.
   *
   * @return The flag.
   *
   * @see #setDomainCrosshairLockedOnData(boolean)
   */
  public boolean isDomainCrosshairLockedOnData() {
    return this.domainCrosshairLockedOnData;
  }

  /**
   * Returns a flag indicating whether or not the domain crosshair is visible.
   *
   * @return The flag.
   *
   * @see #setDomainCrosshairVisible(boolean)
   */
  public boolean isDomainCrosshairVisible() {
    return this.domainCrosshairVisible;
  }

  /**
   * Returns <code>true</code> if the domain gridlines are visible, and
   * <code>false</code> otherwise.
   *
   * @return the visibility flag of domain gridlines.
   */
  public boolean isDomainGridlinesVisible() {
    return this.domainGridlinesVisible;
  }

  /**
   * Returns <code>true</code>
   *
   * @return A boolean.
   */
  public boolean isDomainZoomable() {
    return true;
  }

  /**
   * Returns a flag indicating whether or not the crosshair should "lock-on"
   * to actual data values.
   *
   * @return The flag.
   *
   * @see #setRangeCrosshairLockedOnData(boolean)
   */
  public boolean isRangeCrosshairLockedOnData() {
    return this.rangeCrosshairLockedOnData;
  }

  /**
   * Returns a flag indicating whether or not the range crosshair is visible.
   *
   * @return The flag.
   *
   * @see #setRangeCrosshairVisible(boolean)
   */
  public boolean isRangeCrosshairVisible() {
    return this.rangeCrosshairVisible;
  }

  /**
   * Returns <code>true</code> if the range axis grid is visible, and
   * <code>false<code> otherwise.
   *
   * @return A boolean.
   */
  public boolean isRangeGridlinesVisible() {
    return this.rangeGridlinesVisible;
  }

  /**
   * Returns a flag that controls whether or not a zero baseline is displayed
   * for the range axis.
   *
   * @return A boolean.
   */
  public boolean isRangeZeroBaselineVisible() {
    return this.rangeZeroBaselineVisible;
  }

  /**
   * Returns <code>true</code>
   *
   * @return A boolean.
   */
  public boolean isRangeZoomable() {
    return true;
  }

  /**
   * Maps a dataset to a particular domain axis. All data will be plotted
   * against axis zero by default, no mapping is required for this case.
   *
   * @param index
   *            the dataset index (zero-based).
   * @param axisIndex
   *            the axis index.
   */
  public void mapDatasetToDomainAxis(final int index, final int axisIndex) {
    this.datasetToDomainAxisMap.put(new Integer(index), new Integer(
        axisIndex));
    // fake a dataset change event to update axes...
    this
        .datasetChanged(new DatasetChangeEvent(this, this
            .getDataset(index)));
  }

  /**
   * Maps a dataset to a particular range axis. All data will be plotted
   * against axis zero by default, no mapping is required for this case.
   *
   * @param index
   *            the dataset index (zero-based).
   * @param axisIndex
   *            the axis index.
   */
  public void mapDatasetToRangeAxis(final int index, final int axisIndex) {
    this.datasetToRangeAxisMap.put(new Integer(index), new Integer(
        axisIndex));
    // fake a dataset change event to update axes...
    this
        .datasetChanged(new DatasetChangeEvent(this, this
            .getDataset(index)));
  }

  /**
   * Draws a representation of the data within the dataArea region, using the
   * current renderer.
   * <P>
   * The <code>info</code> and <code>crosshairState</code> arguments may be
   * <code>null</code>.
   *
   * @param gc
   *            the graphics device.
   * @param dataArea
   *            the region in which the data is to be drawn.
   * @param index
   *            the dataset index.
   * @param info
   *            an optional object for collection dimension information.
   *
   * @return A flag that indicates whether any data was actually rendered.
   */
  public boolean render(final GC gc, final Rectangle dataArea,
      final int index, final PlotRenderingInfo info,
      final CrosshairState cstate) {

    final XYDataset dataset = this.getDataset(index);
    if (DatasetUtilities.isEmptyOrNull(dataset)) {
      return false;
    }

    final ValueAxis xAxis = this.getDomainAxisForDataset(index);
    final ValueAxis yAxis = this.getRangeAxisForDataset(index);
    XYItemRenderer renderer = this.getRenderer(index);
    if (renderer == null) {
      renderer = this.getRenderer();
    }

    // assert renderer != null : "Renderer is null";

    final XYItemRendererState state = renderer.initialise(gc, dataArea,
        this, dataset, info);
    final int passCount = renderer.getPassCount();

    final SeriesRenderingOrder seriesOrder = this.getSeriesRenderingOrder();
    if (seriesOrder == SeriesRenderingOrder.REVERSE) {
      // render series in reverse order
      for (int pass = 0; pass < passCount; pass++) {
        final int seriesCount = dataset.getSeriesCount();
        for (int series = seriesCount - 1; series >= 0; series--) {
          final int itemCount = dataset.getItemCount(series);
          for (int item = 0; item < itemCount; item++) {
            renderer.drawItem(gc, state, dataArea, info, this,
                xAxis, yAxis, dataset, series, item, cstate,
                pass);
          }
        }
      }
    } else {
      // render series in forward order
      for (int pass = 0; pass < passCount; pass++) {
        final int seriesCount = dataset.getSeriesCount();
        for (int series = 0; series < seriesCount; series++) {
          final int itemCount = dataset.getItemCount(series);
          for (int item = 0; item < itemCount; item++) {
            renderer.drawItem(gc, state, dataArea, info, this,
                xAxis, yAxis, dataset, series, item, cstate,
                pass);
          }
        }
      }
    }
    return true;
  }

  /**
   * Receives notification of a renderer change event.
   *
   * @param event
   *            the event.
   */
  public void rendererChanged(final RendererChangeEvent event) {
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the axis offsets (gap between the data area and the axes).
   *
   * @param offset
   *            the offset (<code>null</code> not permitted).
   */
  public void setAxisOffset(final RectangleInsets offset) {
    if (offset == null) {
      throw new IllegalArgumentException("Null 'offset' argument.");
    }
    this.axisOffset = offset;
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets a dataset for the plot.
   *
   * @param index
   *            the dataset index.
   * @param dataset
   *            the dataset (<code>null</code> permitted).
   */
  public void setDataset(final int index, final XYDataset dataset) {
    final XYDataset existing = this.getDataset(index);
    if (existing != null) {
      existing.removeChangeListener(this);
    }
    if (index == this.datasets.size()) {
      this.datasets.add(index, dataset);
    } else {
      this.datasets.set(index, dataset);
    }
    if (dataset != null) {
      dataset.addChangeListener(this);
    }

    // send a dataset change event to self...
    final DatasetChangeEvent event = new DatasetChangeEvent(this, dataset);
    this.datasetChanged(event);
  }

  /**
   * Sets the primary dataset for the plot, replacing the existing dataset if
   * there is one.
   *
   * @param dataset
   *            the dataset (<code>null</code> permitted).
   */
  public void setDataset(final XYDataset dataset) {
    this.setDataset(0, dataset);
  }

  /**
   * Sets the rendering order and sends a {@link PlotChangeEvent} to all
   * registered listeners. By default, the plot renders the primary dataset
   * last (so that the primary dataset overlays the secondary datasets). You
   * can reverse this if you want to.
   *
   * @param order
   *            the rendering order (<code>null</code> not permitted).
   */
  public void setDatasetRenderingOrder(final DatasetRenderingOrder order) {
    if (order == null) {
      throw new IllegalArgumentException("Null 'order' argument.");
    }
    this.datasetRenderingOrder = order;
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the domain axes for this plot and sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param axes
   *            the axes.
   */
  public void setDomainAxes(final ValueAxis[] axes) {
    for (int i = 0; i < axes.length; i++) {
      this.setDomainAxis(i, axes[i], false);
    }
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets a domain axis and sends a {@link PlotChangeEvent} to all registered
   * listeners.
   *
   * @param index
   *            the axis index.
   * @param axis
   *            the axis.
   */
  public void setDomainAxis(final int index, final ValueAxis axis) {
    this.setDomainAxis(index, axis, true);
  }

  /**
   * Sets a domain axis and, if requested, sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param index
   *            the axis index.
   * @param axis
   *            the axis.
   * @param notify
   *            notify listeners?
   */
  public void setDomainAxis(final int index, final ValueAxis axis,
      final boolean notify) {
    final ValueAxis existing = this.getDomainAxis(index);
    if (existing != null) {
      existing.removeChangeListener(this);
    }
    if (axis != null) {
      axis.setPlot(this);
    }
    this.domainAxes.set(index, axis);
    if (axis != null) {
      axis.configure();
      axis.addChangeListener(this);
    }
    if (notify) {
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the domain axis for the plot and sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param axis
   *            the new axis (<code>null</code> permitted).
   */
  public void setDomainAxis(final ValueAxis axis) {
    this.setDomainAxis(0, axis);
  }

  /**
   * Sets the location of the domain axis and sends a {@link PlotChangeEvent}
   * to all registered listeners.
   *
   * @param location
   *            the location (<code>null</code> not permitted).
   */
  public void setDomainAxisLocation(final AxisLocation location) {
    // defer argument checking...
    this.setDomainAxisLocation(location, true);
  }

  /**
   * Sets the location of the domain axis and, if requested, sends a
   * {@link PlotChangeEvent} to all registered listeners.
   *
   * @param location
   *            the location (<code>null</code> not permitted).
   * @param notify
   *            notify listeners?
   */
  public void setDomainAxisLocation(final AxisLocation location,
      final boolean notify) {
    if (location == null) {
      throw new IllegalArgumentException("Null 'location' argument.");
    }
    this.domainAxisLocations.set(0, location);
    if (notify) {
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the location for a domain axis and sends a {@link PlotChangeEvent}
   * to all registered listeners.
   *
   * @param index
   *            the axis index.
   * @param location
   *            the location (<code>null</code> permitted).
   */
  public void setDomainAxisLocation(final int index,
      final AxisLocation location) {
    this.domainAxisLocations.set(index, location);
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the flag indicating whether or not the domain crosshair should
   * "lock-on" to actual data values. If the flag value changes, this method
   * sends a {@link PlotChangeEvent} to all registered listeners.
   *
   * @param flag
   *            the flag.
   *
   * @see #isDomainCrosshairLockedOnData()
   */
  public void setDomainCrosshairLockedOnData(final boolean flag) {
    if (this.domainCrosshairLockedOnData != flag) {
      this.domainCrosshairLockedOnData = flag;
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the domain crosshair value and sends a {@link PlotChangeEvent} to
   * all registered listeners (provided that the domain crosshair is visible).
   *
   * @param value
   *            the value.
   *
   * @see #getDomainCrosshairValue()
   */
  public void setDomainCrosshairValue(final double value) {
    this.setDomainCrosshairValue(value, true);
  }

  /**
   * Sets the domain crosshair value and, if requested, sends a
   * {@link PlotChangeEvent} to all registered listeners (provided that the
   * domain crosshair is visible).
   *
   * @param value
   *            the new value.
   * @param notify
   *            notify listeners?
   *
   * @see #getDomainCrosshairValue()
   */
  public void setDomainCrosshairValue(final double value, final boolean notify) {
    this.domainCrosshairValue = value;
    if (this.isDomainCrosshairVisible() && notify) {
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the flag indicating whether or not the domain crosshair is visible
   * and, if the flag changes, sends a {@link PlotChangeEvent} to all
   * registered listeners.
   *
   * @param flag
   *            the new value of the flag.
   *
   * @see #isDomainCrosshairVisible()
   */
  public void setDomainCrosshairVisible(final boolean flag) {
    if (this.domainCrosshairVisible != flag) {
      this.domainCrosshairVisible = flag;
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the paint for the grid lines plotted against the domain axis, and
   * sends a {@link PlotChangeEvent} to all registered listeners.
   *
   * @param paint
   *            the paint (<code>null</code> not permitted).
   *
   * @throws IllegalArgumentException
   *             if <code>paint</code> is <code>null</code>.
   *
   * @see #getDomainGridlinePaint()
   */
  public void setDomainGridlinePaint(final Color paint) {
    if (paint == null) {
      throw new IllegalArgumentException("Null 'paint' argument.");
    }
    this.domainGridlinePaint = paint;
    this.fireChangeEvent();
  }

  /**
   * Sets the stroke for the grid lines plotted against the domain axis, and
   * sends a {@link PlotChangeEvent} to all registered listeners.
   * <p>
   * If you set this to <code>null</code>, no grid lines will be drawn.
   *
   * @param stroke
   *            the stroke (<code>null</code> not permitted).
   *
   * @throws IllegalArgumentException
   *             if <code>stroke</code> is <code>null</code>.
   *
   * @see #getDomainGridlineStroke()
   */
  public void setDomainGridlineStroke(final Stroke stroke) {
    if (stroke == null) {
      throw new IllegalArgumentException("Null 'stroke' argument.");
    }
    this.domainGridlineStroke = stroke;
    this.fireChangeEvent();
  }

  /**
   * Sets the flag that controls whether or not the domain grid-lines are
   * visible.
   * <p>
   * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
   * registered listeners.
   *
   * @param visible
   *            the new value of the flag.
   */
  public void setDomainGridlinesVisible(final boolean visible) {
    if (this.domainGridlinesVisible != visible) {
      this.domainGridlinesVisible = visible;
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the fixed domain axis space.
   *
   * @param space
   *            the space.
   */
  public void setFixedDomainAxisSpace(final AxisSpace space) {
    this.fixedDomainAxisSpace = space;
  }

  /**
   * Sets the fixed legend items for the plot. Leave this set to
   * <code>null</code> if you prefer the legend items to be created
   * automatically.
   *
   * @param items
   *            the legend items (<code>null</code> permitted).
   */
  public void setFixedLegendItems(final LegendItemCollection items) {
    this.fixedLegendItems = items;
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the fixed range axis space.
   *
   * @param space
   *            the space.
   */
  public void setFixedRangeAxisSpace(final AxisSpace space) {
    this.fixedRangeAxisSpace = space;
  }

  /**
   * Sets the orientation for the plot.
   *
   * @param orientation
   *            the orientation (<code>null</code> not allowed).
   */
  public void setOrientation(final PlotOrientation orientation) {
    if (orientation == null) {
      throw new IllegalArgumentException("Null 'orientation' argument.");
    }
    if (orientation != this.orientation) {
      this.orientation = orientation;
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the quadrant origin and sends a {@link PlotChangeEvent} to all
   * registered listeners.
   *
   * @param origin
   *            the origin (<code>null</code> not permitted).
   */
  public void setQuadrantOrigin(final Point origin) {
    if (origin == null) {
      throw new IllegalArgumentException("Null 'origin' argument.");
    }
    this.quadrantOrigin = origin;
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the range axes for this plot and sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param axes
   *            the axes.
   */
  public void setRangeAxes(final ValueAxis[] axes) {
    for (int i = 0; i < axes.length; i++) {
      this.setRangeAxis(i, axes[i], false);
    }
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets a range axis and sends a {@link PlotChangeEvent} to all registered
   * listeners.
   *
   * @param index
   *            the axis index.
   * @param axis
   *            the axis (<code>null</code> permitted).
   */
  public void setRangeAxis(final int index, final ValueAxis axis) {
    this.setRangeAxis(index, axis, true);
  }

  /**
   * Sets a range axis and, if requested, sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param index
   *            the axis index.
   * @param axis
   *            the axis (<code>null</code> permitted).
   * @param notify
   *            notify listeners?
   */
  public void setRangeAxis(final int index, final ValueAxis axis,
      final boolean notify) {
    final ValueAxis existing = this.getRangeAxis(index);
    if (existing != null) {
      existing.removeChangeListener(this);
    }
    if (axis != null) {
      axis.setPlot(this);
    }
    // XXX Grow the list larger if needed
    if (index == this.rangeAxes.size()) {
      this.rangeAxes.add(index, axis);
    } else {
      this.rangeAxes.set(index, axis);
    }
    if (axis != null) {
      axis.configure();
      axis.addChangeListener(this);
    }
    if (notify) {
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the range axis for the plot and sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param axis
   *            the axis (<code>null</code> permitted).
   *
   */
  public void setRangeAxis(final ValueAxis axis) {
    if (axis != null) {
      axis.setPlot(this);
    }

    // plot is likely registered as a listener with the existing axis...
    final ValueAxis existing = this.getRangeAxis();
    if (existing != null) {
      existing.removeChangeListener(this);
    }

    this.rangeAxes.set(0, axis);
    if (axis != null) {
      axis.configure();
      axis.addChangeListener(this);
    }
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the location of the primary range axis and sends a
   * {@link PlotChangeEvent} to all registered listeners.
   *
   * @param location
   *            the location (<code>null</code> not permitted).
   */
  public void setRangeAxisLocation(final AxisLocation location) {
    // defer argument checking...
    this.setRangeAxisLocation(location, true);
  }

  /**
   * Sets the location of the primary range axis and, if requested, sends a
   * {@link PlotChangeEvent} to all registered listeners.
   *
   * @param location
   *            the location (<code>null</code> not permitted).
   * @param notify
   *            notify listeners?
   */
  public void setRangeAxisLocation(final AxisLocation location,
      final boolean notify) {
    if (location == null) {
      throw new IllegalArgumentException("Null 'location' argument.");
    }
    this.rangeAxisLocations.set(0, location);
    if (notify) {
      this.notifyListeners(new PlotChangeEvent(this));
    }

  }

  /**
   * Sets the location for a range axis and sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param index
   *            the axis index.
   * @param location
   *            the location (<code>null</code> permitted).
   */
  public void setRangeAxisLocation(final int index,
      final AxisLocation location) {
    if (index == this.rangeAxisLocations.size()) {
      this.rangeAxisLocations.add(location);
    } else {
      this.rangeAxisLocations.set(index, location);
    }
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the flag indicating whether or not the range crosshair should
   * "lock-on" to actual data values. If the flag value changes, this method
   * sends a {@link PlotChangeEvent} to all registered listeners.
   *
   * @param flag
   *            the flag.
   *
   * @see #isRangeCrosshairLockedOnData()
   */
  public void setRangeCrosshairLockedOnData(final boolean flag) {
    if (this.rangeCrosshairLockedOnData != flag) {
      this.rangeCrosshairLockedOnData = flag;
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the range crosshair value.
   * <P>
   * Registered listeners are notified that the plot has been modified, but
   * only if the crosshair is visible.
   *
   * @param value
   *            the new value.
   *
   * @see #getRangeCrosshairValue()
   */
  public void setRangeCrosshairValue(final double value) {
    this.setRangeCrosshairValue(value, true);
  }

  /**
   * Sets the range crosshair value and sends a {@link PlotChangeEvent} to all
   * registered listeners, but only if the crosshair is visible.
   *
   * @param value
   *            the new value.
   * @param notify
   *            a flag that controls whether or not listeners are notified.
   *
   * @see #getRangeCrosshairValue()
   */
  public void setRangeCrosshairValue(final double value, final boolean notify) {
    this.rangeCrosshairValue = value;
    if (this.isRangeCrosshairVisible() && notify) {
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the flag indicating whether or not the range crosshair is visible.
   * If the flag value changes, this method sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param flag
   *            the new value of the flag.
   *
   * @see #isRangeCrosshairVisible()
   */
  public void setRangeCrosshairVisible(final boolean flag) {
    if (this.rangeCrosshairVisible != flag) {
      this.rangeCrosshairVisible = flag;
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the paint for the grid lines plotted against the range axis and
   * sends a {@link PlotChangeEvent} to all registered listeners.
   *
   * @param paint
   *            the paint (<code>null</code> not permitted).
   *
   * @see #getRangeGridlinePaint()
   */
  public void setRangeGridlinePaint(final Color paint) {
    if (paint == null) {
      throw new IllegalArgumentException("Null 'paint' argument.");
    }
    this.rangeGridlinePaint = paint;
    this.fireChangeEvent();
  }

  /**
   * Sets the stroke for the grid lines plotted against the range axis, and
   * sends a {@link PlotChangeEvent} to all registered listeners.
   *
   * @param stroke
   *            the stroke (<code>null</code> not permitted).
   *
   * @see #getRangeGridlineStroke()
   */
  public void setRangeGridlineStroke(final Stroke stroke) {
    if (stroke == null) {
      throw new IllegalArgumentException("Null 'stroke' argument.");
    }
    this.rangeGridlineStroke = stroke;
    this.fireChangeEvent();
  }

  /**
   * Sets the flag that controls whether or not the range axis grid lines are
   * visible.
   * <p>
   * If the flag value is changed, a {@link PlotChangeEvent} is sent to all
   * registered listeners.
   *
   * @param visible
   *            the new value of the flag.
   */
  public void setRangeGridlinesVisible(final boolean visible) {
    if (this.rangeGridlinesVisible != visible) {
      this.rangeGridlinesVisible = visible;
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the flag that controls whether or not the zero baseline is displayed
   * for the range axis, and sends a {@link PlotChangeEvent} to all registered
   * listeners.
   *
   * @param visible
   *            the flag.
   */
  public void setRangeZeroBaselineVisible(final boolean visible) {
    this.rangeZeroBaselineVisible = visible;
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets a renderer and sends a {@link PlotChangeEvent} to all registered
   * listeners.
   *
   * @param index
   *            the index.
   * @param renderer
   *            the renderer.
   */
  public void setRenderer(final int index, final XYItemRenderer renderer) {
    this.setRenderer(index, renderer, true);
  }

  /**
   * Sets a renderer and sends a {@link PlotChangeEvent} to all registered
   * listeners.
   *
   * @param index
   *            the index.
   * @param renderer
   *            the renderer.
   * @param notify
   *            notify listeners?
   */
  public void setRenderer(final int index, final XYItemRenderer renderer,
      final boolean notify) {
    final XYItemRenderer existing = this.getRenderer(index);
    if (existing != null) {
      existing.removeChangeListener(this);
    }
    if (index < this.renderers.size()) {
      this.renderers.set(index, renderer);
    } else {
      this.renderers.add(index, renderer);
    }
    if (renderer != null) {
      renderer.setPlot(this);
      renderer.addChangeListener(this);
    }
    this.configureDomainAxes();
    this.configureRangeAxes();
    if (notify) {
      this.notifyListeners(new PlotChangeEvent(this));
    }
  }

  /**
   * Sets the renderer for the primary dataset and sends a
   * {@link PlotChangeEvent} to all registered listeners. If the renderer is
   * set to <code>null</code>, no data will be displayed.
   *
   * @param renderer
   *            the renderer (<code>null</code> permitted).
   */
  public void setRenderer(final XYItemRenderer renderer) {
    this.setRenderer(0, renderer);
  }

  /**
   * Sets the renderers for this plot and sends a {@link PlotChangeEvent} to
   * all registered listeners.
   *
   * @param renderers
   *            the renderers.
   */
  public void setRenderers(final XYItemRenderer[] renderers) {
    for (int i = 0; i < renderers.length; i++) {
      this.setRenderer(i, renderers[i], false);
    }
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the series order and sends a {@link PlotChangeEvent} to all
   * registered listeners. By default, the plot renders the primary series
   * last (so that the primary series appears to be on top). You can reverse
   * this if you want to.
   *
   * @param order
   *            the rendering order (<code>null</code> not permitted).
   */
  public void setSeriesRenderingOrder(final SeriesRenderingOrder order) {
    if (order == null) {
      throw new IllegalArgumentException("Null 'order' argument.");
    }
    this.seriesRenderingOrder = order;
    this.notifyListeners(new PlotChangeEvent(this));
  }

  /**
   * Sets the weight for the plot.
   *
   * @param weight
   *            the weight.
   */
  public void setWeight(final int weight) {
    this.weight = weight;
  }

  /**
   * Zooms in on the domain axis/axes. The new lower and upper bounds are
   * specified as percentages of the current axis range, where 0 percent is
   * the current lower bound and 100 percent is the current upper bound.
   *
   * @param lowerPercent
   *            a percentage that determines the new lower bound for the axis
   *            (e.g. 0.20 is twenty percent).
   * @param upperPercent
   *            a percentage that determines the new upper bound for the axis
   *            (e.g. 0.80 is eighty percent).
   * @param info
   *            the plot rendering info.
   * @param source
   *            the source point.
   */
  public void zoomDomainAxes(final double lowerPercent,
      final double upperPercent, final PlotRenderingInfo info,
      final Point source) {
    for (int i = 0; i < this.domainAxes.size(); i++) {
      final ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
      if (domainAxis != null) {
        domainAxis.zoomRange(lowerPercent, upperPercent);
      }
    }
  }

  /**
   * Multiplies the range on the domain axis/axes by the specified factor.
   *
   * @param factor
   *            the zoom factor.
   * @param info
   *            the plot rendering info.
   * @param source
   *            the source point.
   */
  public void zoomDomainAxes(final double factor,
      final PlotRenderingInfo info, final Point source) {
    for (int i = 0; i < this.domainAxes.size(); i++) {
      final ValueAxis domainAxis = (ValueAxis) this.domainAxes.get(i);
      if (domainAxis != null) {
        domainAxis.resizeRange(factor);
      }
    }
  }

  /**
   * Zooms in on the range axes.
   *
   * @param lowerPercent
   *            the lower bound.
   * @param upperPercent
   *            the upper bound.
   * @param info
   *            the plot rendering info.
   * @param source
   *            the source point.
   */
  public void zoomRangeAxes(final double lowerPercent,
      final double upperPercent, final PlotRenderingInfo info,
      final Point source) {
    for (int i = 0; i < this.rangeAxes.size(); i++) {
      final ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
      if (rangeAxis != null) {
        rangeAxis.zoomRange(lowerPercent, upperPercent);
      }
    }
  }

  /**
   * Multiplies the range on the range axis/axes by the specified factor.
   *
   * @param factor
   *            the zoom factor.
   * @param info
   *            the plot rendering info.
   * @param source
   *            the source point.
   */
  public void zoomRangeAxes(final double factor,
      final PlotRenderingInfo info, final Point source) {
    for (int i = 0; i < this.rangeAxes.size(); i++) {
      final ValueAxis rangeAxis = (ValueAxis) this.rangeAxes.get(i);
      if (rangeAxis != null) {
        rangeAxis.resizeRange(factor);
      }
    }
  }

}
TOP

Related Classes of com.positive.charts.plot.xy.XYPlot

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.