/******************************************************************************* * 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 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 final List<IDisposeListener> listeners; /** * Constructor. * * @param chart * the chart * @param id * the series id */ protected Series ( final Chart chart, final String id ) { super (); this.chart = chart; this.id = id; this.xAxisId = 0; this.yAxisId = 0; this.visible = true; this.type = DEFAULT_SERIES_TYPE; this.stackEnabled = false; this.isXMonotoneIncreasing = true; this.seriesLabel = new SeriesLabel (); this.xErrorBar = new ErrorBar (); this.yErrorBar = new ErrorBar (); this.listeners = new ArrayList<IDisposeListener> (); } /* * @see ISeries#getId() */ public String getId () { return this.id; } /* * @see ISeries#setVisible(boolean) */ public void setVisible ( final boolean visible ) { if ( this.visible == visible ) { return; } this.visible = visible; ( (SeriesSet)this.chart.getSeriesSet () ).updateStackAndRiserData (); } /* * @see ISeries#isVisible() */ public boolean isVisible () { return this.visible; } /* * @see ISeries#getType() */ public SeriesType getType () { return this.type; } /* * @see ISeries#isStackEnabled() */ public boolean isStackEnabled () { return this.stackEnabled; } /* * @see ISeries#enableStack(boolean) */ public void enableStack ( final boolean enabled ) { if ( enabled && this.minY < 0 ) { throw new IllegalStateException ( "Stacked series cannot contain minus values." ); } if ( this.stackEnabled == enabled ) { return; } this.stackEnabled = enabled; ( (SeriesSet)this.chart.getSeriesSet () ).updateStackAndRiserData (); } /* * @see ISeries#setXSeries(double[]) */ public void setXSeries ( final double[] series ) { if ( series == null ) { SWT.error ( SWT.ERROR_NULL_ARGUMENT ); return; // to suppress warning... } this.xSeries = new double[series.length]; System.arraycopy ( series, 0, this.xSeries, 0, series.length ); this.isDateSeries = false; if ( this.xSeries.length == 0 ) { return; } // find the min and max value of x series this.minX = this.xSeries[0]; this.maxX = this.xSeries[0]; for ( int i = 1; i < this.xSeries.length; i++ ) { if ( this.minX > this.xSeries[i] ) { this.minX = this.xSeries[i]; } if ( this.maxX < this.xSeries[i] ) { this.maxX = this.xSeries[i]; } if ( this.xSeries[i - 1] > this.xSeries[i] ) { this.isXMonotoneIncreasing = false; } } setCompressor (); this.compressor.setXSeries ( this.xSeries ); if ( this.ySeries != null ) { this.compressor.setYSeries ( this.ySeries ); } if ( this.minX <= 0 ) { final IAxis axis = this.chart.getAxisSet ().getXAxis ( this.xAxisId ); if ( axis != null ) { axis.enableLogScale ( false ); } } } /* * @see ISeries#getXSeries() */ public double[] getXSeries () { if ( this.xSeries == null ) { return null; } final double[] copiedSeries = new double[this.xSeries.length]; System.arraycopy ( this.xSeries, 0, copiedSeries, 0, this.xSeries.length ); return copiedSeries; } /* * @see ISeries#setYSeries(double[]) */ public void setYSeries ( final double[] series ) { if ( series == null ) { SWT.error ( SWT.ERROR_NULL_ARGUMENT ); return; // to suppress warning... } this.ySeries = new double[series.length]; System.arraycopy ( series, 0, this.ySeries, 0, series.length ); if ( this.ySeries.length == 0 ) { return; } // find the min and max value of y series this.minY = this.ySeries[0]; this.maxY = this.ySeries[0]; for ( int i = 1; i < this.ySeries.length; i++ ) { if ( this.minY > this.ySeries[i] ) { this.minY = this.ySeries[i]; } if ( this.maxY < this.ySeries[i] ) { this.maxY = this.ySeries[i]; } } if ( this.xSeries == null || this.xSeries.length != series.length ) { this.xSeries = new double[series.length]; for ( int i = 0; i < series.length; i++ ) { this.xSeries[i] = i; } this.minX = this.xSeries[0]; this.maxX = this.xSeries[this.xSeries.length - 1]; this.isXMonotoneIncreasing = true; } setCompressor (); this.compressor.setXSeries ( this.xSeries ); this.compressor.setYSeries ( this.ySeries ); if ( this.minX <= 0 ) { final IAxis axis = this.chart.getAxisSet ().getXAxis ( this.xAxisId ); if ( axis != null ) { axis.enableLogScale ( false ); } } if ( this.minY <= 0 ) { final IAxis axis = this.chart.getAxisSet ().getYAxis ( this.yAxisId ); if ( axis != null ) { axis.enableLogScale ( false ); } this.stackEnabled = false; } } /* * @see ISeries#getYSeries() */ public double[] getYSeries () { if ( this.ySeries == null ) { return null; } final double[] copiedSeries = new double[this.ySeries.length]; System.arraycopy ( this.ySeries, 0, copiedSeries, 0, this.ySeries.length ); return copiedSeries; } /* * @see ISeries#setXDateSeries(Date[]) */ public void setXDateSeries ( final Date[] series ) { if ( series == null ) { SWT.error ( SWT.ERROR_NULL_ARGUMENT ); return; // to suppress warning... } final double[] xDateSeries = new double[series.length]; for ( int i = 0; i < series.length; i++ ) { if ( series[i] != null ) { xDateSeries[i] = series[i].getTime (); } else { xDateSeries[i] = Double.NaN; } } setXSeries ( xDateSeries ); this.isDateSeries = true; } /* * @see ISeries#getXDateSeries() */ public Date[] getXDateSeries () { if ( !this.isDateSeries ) { return null; } final Date[] series = new Date[this.xSeries.length]; for ( int i = 0; i < series.length; i++ ) { series[i] = new Date ( (long)this.xSeries[i] ); } return series; } /** * Gets the state indicating if date series is set. * * @return true if date series is set */ public boolean isDateSeries () { return this.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 this.stackEnabled && this.stackSeries != null && this.stackSeries.length > 0 && !this.chart.getAxisSet ().getYAxis ( this.yAxisId ).isLogScaleEnabled () && ( (Axis)this.chart.getAxisSet ().getXAxis ( this.xAxisId ) ).isValidCategoryAxis (); } /** * Gets the X range of series. * * @return the X range of series */ public Range getXRange () { double min = this.minX; double max = this.maxX; if ( min == max ) { min = min - 0.5; max = max + 0.5; } return new Range ( min, max ); } /** * 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 () { final double min = this.minY; double max = this.maxY; final Axis xAxis = (Axis)this.chart.getAxisSet ().getXAxis ( this.xAxisId ); if ( isValidStackSeries () && xAxis.isValidCategoryAxis () ) { for ( int i = 0; i < this.stackSeries.length; i++ ) { if ( max < this.stackSeries[i] ) { max = this.stackSeries[i]; } } } return new Range ( min, max ); } /** * Gets the compressor. * * @return the compressor */ protected ICompress getCompressor () { return this.compressor; } /** * Sets the compressor. */ abstract protected void setCompressor (); /* * @see ISeries#getXAxisId() */ public int getXAxisId () { return this.xAxisId; } /* * @see ISeries#setXAxisId(int) */ public void setXAxisId ( final int id ) { if ( this.xAxisId == id ) { return; } final IAxis axis = this.chart.getAxisSet ().getXAxis ( this.xAxisId ); if ( this.minX <= 0 && axis != null && axis.isLogScaleEnabled () ) { this.chart.getAxisSet ().getXAxis ( this.xAxisId ).enableLogScale ( false ); } this.xAxisId = id; ( (SeriesSet)this.chart.getSeriesSet () ).updateStackAndRiserData (); } /* * @see ISeries#getYAxisId() */ public int getYAxisId () { return this.yAxisId; } /* * @see ISeries#setYAxisId(int) */ public void setYAxisId ( final int id ) { this.yAxisId = id; } /* * @see ISeries#getLabel() */ public ISeriesLabel getLabel () { return this.seriesLabel; } /* * @see ISeries#getXErrorBar() */ public IErrorBar getXErrorBar () { return this.xErrorBar; } /* * @see ISeries#getYErrorBar() */ public IErrorBar getYErrorBar () { return this.yErrorBar; } /** * Sets the stack series * * @param stackSeries */ protected void setStackSeries ( final double[] stackSeries ) { this.stackSeries = stackSeries; } /* * @see ISeries#getPixelCoordinates(int) */ public Point getPixelCoordinates ( final int index ) { // get the horizontal and vertical axes IAxis hAxis; IAxis vAxis; if ( this.chart.getOrientation () == SWT.HORIZONTAL ) { hAxis = this.chart.getAxisSet ().getXAxis ( this.xAxisId ); vAxis = this.chart.getAxisSet ().getYAxis ( this.yAxisId ); } else if ( this.chart.getOrientation () == SWT.VERTICAL ) { hAxis = this.chart.getAxisSet ().getYAxis ( this.yAxisId ); vAxis = this.chart.getAxisSet ().getXAxis ( this.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 ( final IAxis axis, final int index ) { // get the data coordinate double dataCoordinate; if ( axis.getDirection () == Direction.X ) { if ( axis.isCategoryEnabled () ) { dataCoordinate = index; } else { if ( index < 0 || this.xSeries.length <= index ) { throw new IllegalArgumentException ( "Series index is out of range." ); //$NON-NLS-1$ } dataCoordinate = this.xSeries[index]; } } else if ( axis.getDirection () == Direction.Y ) { if ( isValidStackSeries () ) { if ( index < 0 || this.stackSeries.length <= index ) { throw new IllegalArgumentException ( "Series index is out of range." ); //$NON-NLS-1$ } dataCoordinate = this.stackSeries[index]; } else { if ( index < 0 || this.ySeries.length <= index ) { throw new IllegalArgumentException ( "Series index is out of range." ); //$NON-NLS-1$ } dataCoordinate = this.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 ( final int lowerPlotMargin, final int upperPlotMargin, final int length, final Axis axis, final Range range ) { if ( length == 0 ) { return range; } final int lowerPixelCoordinate = axis.getPixelCoordinate ( range.lower, range.lower, range.upper ) + lowerPlotMargin * ( axis.isHorizontalAxis () ? -1 : 1 ); final int upperPixelCoordinate = axis.getPixelCoordinate ( range.upper, range.lower, range.upper ) + upperPlotMargin * ( axis.isHorizontalAxis () ? 1 : -1 ); final double lower = axis.getDataCoordinate ( lowerPixelCoordinate, range.lower, range.upper ); final double upper = axis.getDataCoordinate ( upperPixelCoordinate, range.lower, range.upper ); return new Range ( lower, upper ); } /** * Disposes SWT resources. */ protected void dispose () { for ( final IDisposeListener listener : this.listeners ) { listener.disposed ( new Event () ); } } /* * @see IAxis#addDisposeListener(IDisposeListener) */ public void addDisposeListener ( final IDisposeListener listener ) { this.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 ( final GC gc, final int width, final int height ) { if ( !this.visible || width < 0 || height < 0 || this.xSeries == null || this.xSeries.length == 0 || this.ySeries == null || this.ySeries.length == 0 ) { return; } final Axis xAxis = (Axis)this.chart.getAxisSet ().getXAxis ( getXAxisId () ); final Axis yAxis = (Axis)this.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 ); }