/*******************************************************************************
* 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 org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Display;
import org.swtchart.Chart;
import org.swtchart.ILineSeries;
import org.swtchart.LineStyle;
import org.swtchart.Range;
import org.swtchart.IAxis.Direction;
import org.swtchart.internal.Util;
import org.swtchart.internal.axis.Axis;
import org.swtchart.internal.compress.CompressLineSeries;
import org.swtchart.internal.compress.CompressScatterSeries;
/**
* Line series.
*/
public class LineSeries extends Series implements ILineSeries {
/** the symbol size in pixel */
private int symbolSize;
/** the symbol color */
private Color symbolColor;
/** the symbol colors */
private Color[] symbolColors;
/** the symbol type */
private PlotSymbolType symbolType;
/** the line style */
private LineStyle lineStyle;
/** the line color */
private Color lineColor;
/** the line width */
private int lineWidth;
/** the state indicating if area chart is enabled */
private boolean areaEnabled;
/** the state indicating if step chart is enabled */
private boolean stepEnabled;
/** the anti-aliasing value for drawing line */
private int antialias;
/** the alpha value to draw area */
private static final int ALPHA = 50;
/** the default line style */
private static final LineStyle DEFAULT_LINE_STYLE = LineStyle.SOLID;
/** the default line width */
private static final int DEFAULT_LINE_WIDTH = 1;
/** the default line color */
private static final int DEFAULT_LINE_COLOR = SWT.COLOR_BLUE;
/** the default symbol color */
private static final int DEFAULT_SYMBOL_COLOR = SWT.COLOR_DARK_GRAY;
/** the default symbol size */
private static final int DEFAULT_SIZE = 4;
/** the default symbol type */
private static final PlotSymbolType DEFAULT_SYMBOL_TYPE = PlotSymbolType.CIRCLE;
/** the default anti-aliasing value */
private static final int DEFAULT_ANTIALIAS = SWT.DEFAULT;
/** the margin in pixels attached at the minimum/maximum plot */
private static final int MARGIN_AT_MIN_MAX_PLOT = 6;
/**
* Constructor.
*
* @param chart
* the chart
* @param id
* the series id
*/
protected LineSeries(Chart chart, String id) {
super(chart, id);
symbolSize = 4;
symbolColor = Display.getDefault().getSystemColor(DEFAULT_SYMBOL_COLOR);
symbolType = DEFAULT_SYMBOL_TYPE;
lineStyle = DEFAULT_LINE_STYLE;
lineColor = Display.getDefault().getSystemColor(DEFAULT_LINE_COLOR);
areaEnabled = false;
antialias = DEFAULT_ANTIALIAS;
lineWidth = DEFAULT_LINE_WIDTH;
compressor = new CompressLineSeries();
}
/*
* @see ILineSeries#getLineStyle()
*/
public LineStyle getLineStyle() {
return lineStyle;
}
/*
* @see ILineSeries#setLineStyle(LineStyle)
*/
public void setLineStyle(LineStyle style) {
if (style == null) {
this.lineStyle = DEFAULT_LINE_STYLE;
return;
}
this.lineStyle = style;
if (compressor instanceof CompressScatterSeries) {
((CompressScatterSeries) compressor)
.setLineVisible(style != LineStyle.NONE);
}
}
/*
* @see ILineSeries#getLineColor()
*/
public Color getLineColor() {
if (lineColor.isDisposed()) {
lineColor = Display.getDefault().getSystemColor(DEFAULT_LINE_COLOR);
}
return lineColor;
}
/*
* @see ILineSeries#setLineColor(Color)
*/
public void setLineColor(Color color) {
if (color != null && color.isDisposed()) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
if (color == null) {
this.lineColor = Display.getDefault().getSystemColor(
DEFAULT_LINE_COLOR);
} else {
this.lineColor = color;
}
}
/*
* @see ILineSeries#getLineWidth()
*/
public int getLineWidth() {
return lineWidth;
}
/*
* @see ILineSeries#setLineWidth(int)
*/
public void setLineWidth(int width) {
if (width <= 0) {
this.lineWidth = DEFAULT_LINE_WIDTH;
} else {
this.lineWidth = width;
}
}
/*
* @see ILineSeries#getSymbolType()
*/
public PlotSymbolType getSymbolType() {
return symbolType;
}
/*
* @see ILineSeries#setSymbolType(PlotSymbolType)
*/
public void setSymbolType(PlotSymbolType type) {
if (type == null) {
this.symbolType = DEFAULT_SYMBOL_TYPE;
} else {
this.symbolType = type;
}
}
/*
* @see ILineSeries#getSymbolSize()
*/
public int getSymbolSize() {
return symbolSize;
}
/*
* @see ILineSeries#setSymbolSize(int)
*/
public void setSymbolSize(int size) {
if (size <= 0) {
this.symbolSize = DEFAULT_SIZE;
} else {
this.symbolSize = size;
}
}
/*
* @see ILineSeries#getSymbolColor()
*/
public Color getSymbolColor() {
return symbolColor;
}
/*
* @see ILineSeries#setSymbolColor(Color)
*/
public void setSymbolColor(Color color) {
if (color != null && color.isDisposed()) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
if (color == null) {
this.symbolColor = Display.getDefault().getSystemColor(
DEFAULT_SYMBOL_COLOR);
} else {
this.symbolColor = color;
}
}
/*
* @see ILineSeries#getSymbolColors()
*/
public Color[] getSymbolColors() {
if (symbolColors == null) {
return null;
}
Color[] copiedSymbolColors = new Color[symbolColors.length];
System.arraycopy(symbolColors, 0, copiedSymbolColors, 0,
symbolColors.length);
return copiedSymbolColors;
}
/*
* @see ILineSeries#setSymbolColors(Color [])
*/
public void setSymbolColors(Color[] colors) {
if (colors == null) {
symbolColors = null;
return;
}
for (Color color : colors) {
if (color.isDisposed()) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
}
symbolColors = new Color[colors.length];
System.arraycopy(colors, 0, symbolColors, 0, colors.length);
}
/*
* @see Series#setCompressor()
*/
@Override
protected void setCompressor() {
if (isXMonotoneIncreasing) {
compressor = new CompressLineSeries();
} else {
compressor = new CompressScatterSeries();
((CompressScatterSeries) compressor)
.setLineVisible(getLineStyle() != LineStyle.NONE);
}
}
/*
* @see ILineSeries#enableArea(boolean)
*/
public void enableArea(boolean enabled) {
areaEnabled = enabled;
}
/*
* @see ILineSeries#isAreaEnabled()
*/
public boolean isAreaEnabled() {
return areaEnabled;
}
/*
* @see ILineSeries#enableStep(boolean)
*/
public void enableStep(boolean enabled) {
stepEnabled = enabled;
}
/*
* @see ILineSeries#isStepEnabled()
*/
public boolean isStepEnabled() {
return stepEnabled;
}
/*
* @see Series#getAdjustedRange(Axis, int)
*/
@Override
public Range getAdjustedRange(Axis axis, int length) {
Range range;
if (axis.getDirection() == Direction.X) {
range = getXRange();
} else {
range = getYRange();
}
int lowerPlotMargin = getSymbolSize() + MARGIN_AT_MIN_MAX_PLOT;
int upperPlotMargin = getSymbolSize() + MARGIN_AT_MIN_MAX_PLOT;
return getRangeWithMargin(lowerPlotMargin, upperPlotMargin, length,
axis, range);
}
/*
* @see ILineSeries#getAntialias()
*/
public int getAntialias() {
return antialias;
}
/*
* @see ILineSeries#setAntialias(int)
*/
public void setAntialias(int antialias) {
if (antialias != SWT.DEFAULT && antialias != SWT.ON
&& antialias != SWT.OFF) {
SWT.error(SWT.ERROR_INVALID_ARGUMENT);
}
this.antialias = antialias;
}
/**
* Gets the line points to draw line and area.
*
* @param xseries
* the horizontal series
* @param yseries
* the vertical series
* @param indexes
* the series indexes
* @param index
* the index of series
* @param xAxis
* the X axis
* @param yAxis
* the Y axis
* @return the line points
*/
private int[] getLinePoints(double[] xseries, double[] yseries,
int[] indexes, int index, Axis xAxis, Axis yAxis) {
int x1 = xAxis.getPixelCoordinate(xseries[index]);
int x2 = xAxis.getPixelCoordinate(xseries[index + 1]);
int x3 = x2;
int x4 = x1;
int y1 = yAxis.getPixelCoordinate(yseries[index]);
int y2 = yAxis.getPixelCoordinate(yseries[index + 1]);
int y3, y4;
double baseYCoordinate = yAxis.getRange().lower > 0 ? yAxis.getRange().lower
: 0;
if (yAxis.isLogScaleEnabled()) {
y3 = yAxis.getPixelCoordinate(yAxis.getRange().lower);
y4 = y3;
} else if (isValidStackSeries()) {
y1 = yAxis.getPixelCoordinate(stackSeries[indexes[index]]);
y2 = yAxis.getPixelCoordinate(stackSeries[indexes[index + 1]]);
y3 = yAxis.getPixelCoordinate(stackSeries[indexes[index + 1]])
+ Math.abs(yAxis.getPixelCoordinate(yseries[index + 1])
- yAxis.getPixelCoordinate(0))
* (xAxis.isHorizontalAxis() ? 1 : -1);
y4 = yAxis.getPixelCoordinate(stackSeries[indexes[index]])
+ Math.abs(yAxis.getPixelCoordinate(yseries[index])
- yAxis.getPixelCoordinate(0))
* (xAxis.isHorizontalAxis() ? 1 : -1);
} else {
y3 = yAxis.getPixelCoordinate(baseYCoordinate);
y4 = y3;
}
if (xAxis.isHorizontalAxis()) {
return new int[] { x1, y1, x2, y2, x3, y3, x4, y4 };
}
return new int[] { y1, x1, y2, x2, y3, x3, y4, x4 };
}
/*
* @see Series#draw(GC, int, int, Axis, Axis)
*/
@Override
protected void draw(GC gc, int width, int height, Axis xAxis, Axis yAxis) {
int oldAntialias = gc.getAntialias();
int oldLineWidth = gc.getLineWidth();
gc.setAntialias(antialias);
gc.setLineWidth(lineWidth);
if (lineStyle != LineStyle.NONE) {
drawLineAndArea(gc, width, height, xAxis, yAxis);
}
if (symbolType != PlotSymbolType.NONE || getLabel().isVisible()
|| getXErrorBar().isVisible() || getYErrorBar().isVisible()) {
drawSymbolAndLabel(gc, width, height, xAxis, yAxis);
}
gc.setAntialias(oldAntialias);
gc.setLineWidth(oldLineWidth);
}
/**
* Draws the line and area.
*
* @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
*/
private void drawLineAndArea(GC gc, int width, int height, Axis xAxis,
Axis yAxis) {
// get x and y series
double[] xseries = compressor.getCompressedXSeries();
double[] yseries = compressor.getCompressedYSeries();
if (xseries.length == 0 || yseries.length == 0){
return;
}
int[] indexes = compressor.getCompressedIndexes();
if (xAxis.isValidCategoryAxis()) {
for (int i = 0; i < xseries.length; i++) {
xseries[i] = indexes[i];
}
}
gc.setLineStyle(Util.getIndexDefinedInSWT(lineStyle));
gc.setForeground(getLineColor());
boolean isHorizontal = xAxis.isHorizontalAxis();
if (stepEnabled || areaEnabled || stackEnabled) {
for (int i = 0; i < xseries.length - 1; i++) {
int[] p = getLinePoints(xseries, yseries, indexes, i, xAxis,
yAxis);
// draw line
if (lineStyle != LineStyle.NONE) {
if (stepEnabled) {
if (isHorizontal) {
gc.drawLine(p[0], p[1], p[2], p[1]);
gc.drawLine(p[2], p[1], p[2], p[3]);
} else {
gc.drawLine(p[0], p[1], p[0], p[3]);
gc.drawLine(p[0], p[3], p[2], p[3]);
}
} else {
gc.drawLine(p[0], p[1], p[2], p[3]);
}
}
// draw area
if (areaEnabled) {
drawArea(gc, p, isHorizontal);
}
}
} else {
double xLower = xAxis.getRange().lower;
double xUpper = xAxis.getRange().upper;
double yLower = yAxis.getRange().lower;
double yUpper = yAxis.getRange().upper;
int prevX = xAxis.getPixelCoordinate(xseries[0], xLower, xUpper);
int prevY = yAxis.getPixelCoordinate(yseries[0], yLower, yUpper);
for (int i = 0; i < xseries.length - 1; i++) {
int x = xAxis
.getPixelCoordinate(xseries[i + 1], xLower, xUpper);
int y = yAxis
.getPixelCoordinate(yseries[i + 1], yLower, yUpper);
if (isHorizontal) {
gc.drawLine(prevX, prevY, x, y);
} else {
gc.drawLine(prevY, prevX, y, x);
}
prevX = x;
prevY = y;
}
}
}
/**
* Draws the area.
*
* @param gc
* the graphic context
* @param p
* the line points
* @param isHorizontal
* true if orientation is horizontal
*/
private void drawArea(GC gc, int[] p, boolean isHorizontal) {
int alpha = gc.getAlpha();
gc.setAlpha(ALPHA);
gc.setBackground(getLineColor());
int[] pointArray;
if (stepEnabled) {
if (isHorizontal) {
pointArray = new int[] { p[0], p[1], p[2], p[1], p[4], p[7],
p[6], p[7], p[0], p[1] };
} else {
pointArray = new int[] { p[0], p[1], p[0], p[3], p[6], p[5],
p[6], p[7], p[0], p[1] };
}
} else {
pointArray = new int[] { p[0], p[1], p[2], p[3], p[4], p[5], p[6],
p[7], p[0], p[1] };
}
gc.fillPolygon(pointArray);
gc.setAlpha(alpha);
}
/**
* Draws series symbol, label and error bars.
*
* @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
*/
private void drawSymbolAndLabel(GC gc, int width, int height, Axis xAxis,
Axis yAxis) {
// get x and y series
double[] xseries = compressor.getCompressedXSeries();
double[] yseries = compressor.getCompressedYSeries();
int[] indexes = compressor.getCompressedIndexes();
if (xAxis.isValidCategoryAxis()) {
boolean isValidStackSeries = isValidStackSeries();
for (int i = 0; i < xseries.length; i++) {
xseries[i] = indexes[i];
if (isValidStackSeries) {
yseries[i] = stackSeries[indexes[i]];
}
}
}
// draw symbol and label
for (int i = 0; i < xseries.length; i++) {
Color color = getSymbolColor();
if (symbolColors != null && symbolColors.length > i) {
color = symbolColors[i];
}
int h, v;
if (xAxis.isHorizontalAxis()) {
h = xAxis.getPixelCoordinate(xseries[i]);
v = yAxis.getPixelCoordinate(yseries[i]);
} else {
v = xAxis.getPixelCoordinate(xseries[i]);
h = yAxis.getPixelCoordinate(yseries[i]);
}
if (getSymbolType() != PlotSymbolType.NONE) {
drawSeriesSymbol(gc, h, v, color);
}
seriesLabel.draw(gc, h, v, yseries[i], indexes[i], SWT.BOTTOM);
xErrorBar.draw(gc, h, v, xAxis, indexes[i]);
yErrorBar.draw(gc, h, v, yAxis, indexes[i]);
}
}
/**
* Draws series symbol.
*
* @param gc
* the GC object
* @param h
* the horizontal coordinate to draw symbol
* @param v
* the vertical coordinate to draw symbol
* @param color
* the symbol color
*/
public void drawSeriesSymbol(GC gc, int h, int v, Color color) {
int oldAntialias = gc.getAntialias();
gc.setAntialias(SWT.ON);
gc.setForeground(color);
gc.setBackground(color);
switch (symbolType) {
case CIRCLE:
gc.fillOval(h - symbolSize, v - symbolSize, symbolSize * 2,
symbolSize * 2);
break;
case SQUARE:
gc.fillRectangle(h - symbolSize, v - symbolSize, symbolSize * 2,
symbolSize * 2);
break;
case DIAMOND:
int[] diamondArray = { h, v - symbolSize, h + symbolSize, v, h,
v + symbolSize, h - symbolSize, v };
gc.fillPolygon(diamondArray);
break;
case TRIANGLE:
int[] triangleArray = { h, v - symbolSize, h + symbolSize,
v + symbolSize, h - symbolSize, v + symbolSize };
gc.fillPolygon(triangleArray);
break;
case INVERTED_TRIANGLE:
int[] invertedTriangleArray = { h, v + symbolSize, h + symbolSize,
v - symbolSize, h - symbolSize, v - symbolSize };
gc.fillPolygon(invertedTriangleArray);
break;
case CROSS:
gc.setLineStyle(SWT.LINE_SOLID);
gc.drawLine(h - symbolSize, v - symbolSize, h + symbolSize, v
+ symbolSize);
gc.drawLine(h - symbolSize, v + symbolSize, h + symbolSize, v
- symbolSize);
break;
case PLUS:
gc.setLineStyle(SWT.LINE_SOLID);
gc.drawLine(h, v - symbolSize, h, v + symbolSize);
gc.drawLine(h - symbolSize, v, h + symbolSize, v);
break;
case NONE:
default:
break;
}
gc.setAntialias(oldAntialias);
}
}