/*
* Copyright (c) 2008 Stiftung Deutsches Elektronen-Synchrotron,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY.
*
* THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS.
* WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE
* IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR
* CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE.
* NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
* DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
* OR MODIFICATIONS.
* THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION,
* USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS
* PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY
* AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM
*/
package org.csstudio.sds.components.ui.internal.figures;
/**
* A strip chart figure.
*
* @author Joerg Rathlev
*/
public final class StripChartFigure extends AbstractChartFigure {
/**
* The maximum number of values that are recorded for a channel.
*/
private static final int MAX_BUFFER_SIZE = 16000;
/**
* Array containing one buffer of values per channel.
*/
private RingBuffer[] _values;
/**
* The number of values to be recorded per channel. Note that this will be
* larger than the capacity of each ring buffer if this value exceeds
* {@link #MAX_BUFFER_SIZE}.
*/
private int _valuesPerChannel;
/**
* The timespan covered by the x-axis.
*/
private double _xAxisTimespan;
/**
* The greatest data value.
*/
private double _greatestDataValue;
/**
* The lowest data value.
*/
private double _lowestDataValue;
/**
* The timespan between the first and the last data value for a series. This
* value may be slightly different from the x-axis timespan due to rounding
* differences.
*/
private double _dataSeriesTimespan;
/**
* Creates a new strip chart figure.
*
* @param numberOfChannels
* the number of channels to be supported by this figure.
* @param valuesPerChannel
* the number of values to be displayed per channel.
* @param dataSeriesTimespan
* the time at which the last value in a series is recorded, in
* seconds. This value can be slightly greater than the length of
* the x-axis because the last value may be to the left of the
* x-axis. For example, if the x-axis length is 10 seconds and
* the update interval is 4 seconds, the
* <code>dataSeriesTimespan</code> will be 12 seconds.
*/
public StripChartFigure(final int numberOfChannels,
final int valuesPerChannel, final double dataSeriesTimespan) {
super(numberOfChannels);
_valuesPerChannel = valuesPerChannel;
_values = new RingBuffer[numberOfChannels];
_dataSeriesTimespan = dataSeriesTimespan;
for (int i = 0; i < _values.length; i++) {
_values[i] = new RingBuffer(Math.min(valuesPerChannel, MAX_BUFFER_SIZE));
}
}
/**
* Sets the x-axis timespan of this figure.
*
* @param timespan
* the timespan in seconds.
*/
public void setXAxisTimespan(final double timespan) {
_xAxisTimespan = timespan;
xAxisRangeChanged();
}
/**
* Adds the next value for the data series with the specified index to the
* plot. This figure does not itself keep track of the exact times at which
* this method is called. It is assumed that this method is called by the
* edit part at a regular interval.
*
* @param index
* the data index.
* @param value
* the current value.
*/
public synchronized void addValue(final int index, final double value) {
_values[index].addValue(value);
if (value > _greatestDataValue) {
_greatestDataValue = value;
dataRangeChanged();
} else if (value < _lowestDataValue) {
_lowestDataValue = value;
dataRangeChanged();
}
}
/**
* {@inheritDoc}
*/
@Override
protected void dataValues(final int index,
final IDataPointProcessor processor) {
_values[index].processValues(processor);
}
/**
* {@inheritDoc}
*/
@Override
protected double greatestDataValue() {
return _greatestDataValue;
}
/**
* {@inheritDoc}
*/
@Override
protected double lowestDataValue() {
return _lowestDataValue;
}
/**
* Returns the greatest x-axis value. For a strip chart, this is always zero
* (the time of the latest recorded value). Note that for a strip chart, the
* x-axis maximum is lower than the x-axis minimum.
*
* @return zero.
*/
@Override
protected double xAxisMaximum() {
return 0.0;
}
/**
* Returns the lowest x-axis value. For a strip chart, this is the x-axis
* timespan value, which is the age of the oldest recorded value in seconds.
* Note that for a strip chart, the x-axis maximum is lower than the x-axis
* minimum.
*
* @return the lowest x-axis value.
*/
@Override
protected double xAxisMinimum() {
return _xAxisTimespan;
}
/**
* A simple ring buffer for <code>double</code> values.
*
* @author Joerg Rathlev
*/
private final class RingBuffer {
/**
* The values stored in the buffer.
*/
private double[] _values;
/**
* The next write index.
*/
private int _nextWriteIndex;
/**
* The number of values stored in the buffer.
*/
private int _size;
/**
* Creates a new, empty buffer with the specified capacity.
*
* @param capacity
* the capacity.
*/
private RingBuffer(final int capacity) {
_values = new double[capacity];
_nextWriteIndex = 0;
_size = 0;
}
/**
* Adds a value to this buffer.
*
* @param value
* the value.
*/
private synchronized void addValue(final double value) {
_values[_nextWriteIndex++] = value;
if (_size < _values.length) {
_size++;
}
if (_nextWriteIndex >= _values.length) {
_nextWriteIndex = 0;
}
}
/**
* Processes the values in this buffer with the specified processor.
* Values are processed in reverse, i.e., the latest value is processed
* first and the oldest value is processed last.
*
* @param processor
* the processor.
*/
private synchronized void processValues(final IDataPointProcessor processor) {
int counter = 0;
int i = _nextWriteIndex;
while (counter < _size) {
if (--i < 0) {
i = _values.length - 1;
}
double xValue = _dataSeriesTimespan * (((double) counter) / (((double) _valuesPerChannel) - 1));
processor.processDataPoint(xValue, _values[i]);
counter++;
}
}
}
}