/******************************************************************************* * Copyright (c) 2008-2009 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; 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 ( 0 ); 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 ); } }