/*******************************************************************************
* Copyright (c) 2008-2011 SWTChart project. All rights reserved.
*
* This code is distributed under the terms of the Eclipse Public License v1.0
* which is available at http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.swtchart.internal.series;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Event;
import org.swtchart.Chart;
import org.swtchart.IAxis;
import org.swtchart.IDisposeListener;
import org.swtchart.IErrorBar;
import org.swtchart.ISeries;
import org.swtchart.ISeriesLabel;
import org.swtchart.Range;
import org.swtchart.IAxis.Direction;
import org.swtchart.internal.axis.Axis;
import org.swtchart.internal.compress.ICompress;
/**
* Series.
*/
abstract public class Series implements ISeries {
/** the default series type */
protected static final SeriesType DEFAULT_SERIES_TYPE = SeriesType.LINE;
/** the x series */
protected double[] xSeries;
/** the y series */
protected double[] ySeries;
/** the minimum value of x series */
protected double minX;
/** the maximum value of x series */
protected double maxX;
/** the minimum value of y series */
protected double minY;
/** the maximum value of y series */
protected double maxY;
/** the series id */
protected String id;
/** the compressor */
protected ICompress compressor;
/** the x axis id */
protected int xAxisId;
/** the y axis id */
protected int yAxisId;
/** the visibility of series */
protected boolean visible;
/** the state indicating whether x series are monotone increasing */
protected boolean isXMonotoneIncreasing;
/** the series type */
protected SeriesType type;
/** the series label */
protected SeriesLabel seriesLabel;
/** the x error bar */
protected ErrorBar xErrorBar;
/** the y error bar */
protected ErrorBar yErrorBar;
/** the chart */
protected Chart chart;
/** the state indicating if the series is a stacked type */
protected boolean stackEnabled;
/** the stack series */
protected double[] stackSeries;
/** the state indicating if the type of X series is <tt>Date</tt> */
private boolean isDateSeries;
/** the list of dispose listeners */
private List<IDisposeListener> listeners;
/**
* Constructor.
*
* @param chart
* the chart
* @param id
* the series id
*/
protected Series(Chart chart, String id) {
super();
this.chart = chart;
this.id = id;
xAxisId = 0;
yAxisId = 0;
visible = true;
type = DEFAULT_SERIES_TYPE;
stackEnabled = false;
isXMonotoneIncreasing = true;
seriesLabel = new SeriesLabel();
xErrorBar = new ErrorBar();
yErrorBar = new ErrorBar();
listeners = new ArrayList<IDisposeListener>();
}
/*
* @see ISeries#getId()
*/
public String getId() {
return id;
}
/*
* @see ISeries#setVisible(boolean)
*/
public void setVisible(boolean visible) {
if (this.visible == visible) {
return;
}
this.visible = visible;
((SeriesSet) chart.getSeriesSet()).updateStackAndRiserData();
}
/*
* @see ISeries#isVisible()
*/
public boolean isVisible() {
return visible;
}
/*
* @see ISeries#getType()
*/
public SeriesType getType() {
return type;
}
/*
* @see ISeries#isStackEnabled()
*/
public boolean isStackEnabled() {
return stackEnabled;
}
/*
* @see ISeries#enableStack(boolean)
*/
public void enableStack(boolean enabled) {
if (enabled && minY < 0) {
throw new IllegalStateException(
"Stacked series cannot contain minus values.");
}
if (stackEnabled == enabled) {
return;
}
stackEnabled = enabled;
((SeriesSet) chart.getSeriesSet()).updateStackAndRiserData();
}
/*
* @see ISeries#setXSeries(double[])
*/
public void setXSeries(double[] series) {
if (series == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
return; // to suppress warning...
}
xSeries = new double[series.length];
System.arraycopy(series, 0, xSeries, 0, series.length);
isDateSeries = false;
if (xSeries.length == 0) {
return;
}
// find the min and max value of x series
minX = xSeries[0];
maxX = xSeries[0];
for (int i = 1; i < xSeries.length; i++) {
if (minX > xSeries[i]) {
minX = xSeries[i];
}
if (maxX < xSeries[i]) {
maxX = xSeries[i];
}
if (xSeries[i - 1] > xSeries[i]) {
isXMonotoneIncreasing = false;
}
}
setCompressor();
compressor.setXSeries(xSeries);
if (ySeries != null) {
compressor.setYSeries(ySeries);
}
if (minX <= 0) {
IAxis axis = chart.getAxisSet().getXAxis(xAxisId);
if (axis != null) {
axis.enableLogScale(false);
}
}
}
/*
* @see ISeries#getXSeries()
*/
public double[] getXSeries() {
if (xSeries == null) {
return null;
}
double[] copiedSeries = new double[xSeries.length];
System.arraycopy(xSeries, 0, copiedSeries, 0, xSeries.length);
return copiedSeries;
}
/*
* @see ISeries#setYSeries(double[])
*/
public void setYSeries(double[] series) {
if (series == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
return; // to suppress warning...
}
ySeries = new double[series.length];
System.arraycopy(series, 0, ySeries, 0, series.length);
if (ySeries.length == 0) {
return;
}
// find the min and max value of y series
minY = ySeries[0];
maxY = ySeries[0];
for (int i = 1; i < ySeries.length; i++) {
if (minY > ySeries[i]) {
minY = ySeries[i];
}
if (maxY < ySeries[i]) {
maxY = ySeries[i];
}
}
if (xSeries == null || xSeries.length != series.length) {
xSeries = new double[series.length];
for (int i = 0; i < series.length; i++) {
xSeries[i] = i;
}
minX = xSeries[0];
maxX = xSeries[xSeries.length - 1];
isXMonotoneIncreasing = true;
}
setCompressor();
compressor.setXSeries(xSeries);
compressor.setYSeries(ySeries);
if (minX <= 0) {
IAxis axis = chart.getAxisSet().getXAxis(xAxisId);
if (axis != null) {
axis.enableLogScale(false);
}
}
if (minY <= 0) {
IAxis axis = chart.getAxisSet().getYAxis(yAxisId);
if (axis != null) {
axis.enableLogScale(false);
}
stackEnabled = false;
}
}
/*
* @see ISeries#getYSeries()
*/
public double[] getYSeries() {
if (ySeries == null) {
return null;
}
double[] copiedSeries = new double[ySeries.length];
System.arraycopy(ySeries, 0, copiedSeries, 0, ySeries.length);
return copiedSeries;
}
/*
* @see ISeries#setXDateSeries(Date[])
*/
public void setXDateSeries(Date[] series) {
if (series == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
return; // to suppress warning...
}
double[] xDateSeries = new double[series.length];
for (int i = 0; i < series.length; i++) {
xDateSeries[i] = series[i].getTime();
}
setXSeries(xDateSeries);
isDateSeries = true;
}
/*
* @see ISeries#getXDateSeries()
*/
public Date[] getXDateSeries() {
if (!isDateSeries) {
return null;
}
Date[] series = new Date[xSeries.length];
for (int i = 0; i < series.length; i++) {
series[i] = new Date((long) xSeries[i]);
}
return series;
}
/**
* Gets the state indicating if date series is set.
*
* @return true if date series is set
*/
public boolean isDateSeries() {
return isDateSeries;
}
/**
* Gets the state indicating if the series is valid stack series.
*
* @return true if the series is valid stack series
*/
public boolean isValidStackSeries() {
return stackEnabled
&& stackSeries != null
&& stackSeries.length > 0
&& !chart.getAxisSet().getYAxis(yAxisId).isLogScaleEnabled()
&& ((Axis) chart.getAxisSet().getXAxis(xAxisId))
.isValidCategoryAxis();
}
/**
* Gets the X range of series.
*
* @return the X range of series
*/
public Range getXRange() {
return new Range(minX, maxX);
}
/**
* Gets the adjusted range to show all series in screen. This range includes
* the size of plot like symbol or bar.
*
* @param axis
* the axis
* @param length
* the axis length in pixels
* @return the adjusted range
*/
abstract public Range getAdjustedRange(Axis axis, int length);
/**
* Gets the Y range of series.
*
* @return the Y range of series
*/
public Range getYRange() {
double min = minY;
double max = maxY;
Axis xAxis = (Axis) chart.getAxisSet().getXAxis(xAxisId);
if (isValidStackSeries() && xAxis.isValidCategoryAxis()) {
for (int i = 0; i < stackSeries.length; i++) {
if (max < stackSeries[i]) {
max = stackSeries[i];
}
}
}
return new Range(min, max);
}
/**
* Gets the compressor.
*
* @return the compressor
*/
protected ICompress getCompressor() {
return compressor;
}
/**
* Sets the compressor.
*/
abstract protected void setCompressor();
/*
* @see ISeries#getXAxisId()
*/
public int getXAxisId() {
return xAxisId;
}
/*
* @see ISeries#setXAxisId(int)
*/
public void setXAxisId(int id) {
if (xAxisId == id) {
return;
}
IAxis axis = chart.getAxisSet().getXAxis(xAxisId);
if (minX <= 0 && axis != null && axis.isLogScaleEnabled()) {
chart.getAxisSet().getXAxis(xAxisId).enableLogScale(false);
}
xAxisId = id;
((SeriesSet) chart.getSeriesSet()).updateStackAndRiserData();
}
/*
* @see ISeries#getYAxisId()
*/
public int getYAxisId() {
return yAxisId;
}
/*
* @see ISeries#setYAxisId(int)
*/
public void setYAxisId(int id) {
yAxisId = id;
}
/*
* @see ISeries#getLabel()
*/
public ISeriesLabel getLabel() {
return seriesLabel;
}
/*
* @see ISeries#getXErrorBar()
*/
public IErrorBar getXErrorBar() {
return xErrorBar;
}
/*
* @see ISeries#getYErrorBar()
*/
public IErrorBar getYErrorBar() {
return yErrorBar;
}
/**
* Sets the stack series
*
* @param stackSeries
* The stack series
*/
protected void setStackSeries(double[] stackSeries) {
this.stackSeries = stackSeries;
}
/*
* @see ISeries#getPixelCoordinates(int)
*/
public Point getPixelCoordinates(int index) {
// get the horizontal and vertical axes
IAxis hAxis;
IAxis vAxis;
if (chart.getOrientation() == SWT.HORIZONTAL) {
hAxis = chart.getAxisSet().getXAxis(xAxisId);
vAxis = chart.getAxisSet().getYAxis(yAxisId);
} else if (chart.getOrientation() == SWT.VERTICAL) {
hAxis = chart.getAxisSet().getYAxis(yAxisId);
vAxis = chart.getAxisSet().getXAxis(xAxisId);
} else {
throw new IllegalStateException("unknown chart orientation"); //$NON-NLS-1$
}
// get the pixel coordinates
return new Point(getPixelCoordinate(hAxis, index), getPixelCoordinate(
vAxis, index));
}
/**
* Gets the pixel coordinates with given axis and series index.
*
* @param axis
* the axis
* @param index
* the series index
* @return the pixel coordinates
*/
private int getPixelCoordinate(IAxis axis, int index) {
// get the data coordinate
double dataCoordinate;
if (axis.getDirection() == Direction.X) {
if (axis.isCategoryEnabled()) {
dataCoordinate = index;
} else {
if (index < 0 || xSeries.length <= index) {
throw new IllegalArgumentException(
"Series index is out of range."); //$NON-NLS-1$
}
dataCoordinate = xSeries[index];
}
} else if (axis.getDirection() == Direction.Y) {
if (isValidStackSeries()) {
if (index < 0 || stackSeries.length <= index) {
throw new IllegalArgumentException(
"Series index is out of range."); //$NON-NLS-1$
}
dataCoordinate = stackSeries[index];
} else {
if (index < 0 || ySeries.length <= index) {
throw new IllegalArgumentException(
"Series index is out of range."); //$NON-NLS-1$
}
dataCoordinate = ySeries[index];
}
} else {
throw new IllegalStateException("unknown axis direction"); //$NON-NLS-1$
}
// get the pixel coordinate
return axis.getPixelCoordinate(dataCoordinate);
}
/**
* Gets the range with given margin.
*
* @param lowerPlotMargin
* the lower margin in pixels
* @param upperPlotMargin
* the upper margin in pixels
* @param length
* the axis length in pixels
* @param axis
* the axis
* @param range
* the range
* @return the range with margin
*/
protected Range getRangeWithMargin(int lowerPlotMargin,
int upperPlotMargin, int length, Axis axis, Range range) {
if (length == 0) {
return range;
}
int lowerPixelCoordinate = axis.getPixelCoordinate(range.lower,
range.lower, range.upper)
+ lowerPlotMargin * (axis.isHorizontalAxis() ? -1 : 1);
int upperPixelCoordinate = axis.getPixelCoordinate(range.upper,
range.lower, range.upper)
+ upperPlotMargin * (axis.isHorizontalAxis() ? 1 : -1);
double lower = axis.getDataCoordinate(lowerPixelCoordinate,
range.lower, range.upper);
double upper = axis.getDataCoordinate(upperPixelCoordinate,
range.lower, range.upper);
return new Range(lower, upper);
}
/**
* Disposes SWT resources.
*/
protected void dispose() {
for (IDisposeListener listener : listeners) {
listener.disposed(new Event());
}
}
/*
* @see IAxis#addDisposeListener(IDisposeListener)
*/
public void addDisposeListener(IDisposeListener listener) {
listeners.add(listener);
}
/**
* Draws series.
*
* @param gc
* the graphics context
* @param width
* the width to draw series
* @param height
* the height to draw series
*/
public void draw(GC gc, int width, int height) {
if (!visible || width < 0 || height < 0 || xSeries == null
|| xSeries.length == 0 || ySeries == null
|| ySeries.length == 0) {
return;
}
Axis xAxis = (Axis) chart.getAxisSet().getXAxis(getXAxisId());
Axis yAxis = (Axis) chart.getAxisSet().getYAxis(getYAxisId());
if (xAxis == null || yAxis == null) {
return;
}
draw(gc, width, height, xAxis, yAxis);
}
/**
* Draws series.
*
* @param gc
* the graphics context
* @param width
* the width to draw series
* @param height
* the height to draw series
* @param xAxis
* the x axis
* @param yAxis
* the y axis
*/
abstract protected void draw(GC gc, int width, int height, Axis xAxis,
Axis yAxis);
}