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);
}
}
}
}