/*******************************************************************************
* Copyright (c) 2010, 2013 Oak Ridge National Laboratory and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Xihui Chen - initial API and implementation
* Max Hohenegger - Bug 418168
******************************************************************************/
package org.eclipse.nebula.visualization.xygraph.dataprovider;
import java.util.Calendar;
import java.util.Iterator;
import org.eclipse.nebula.visualization.xygraph.linearscale.Range;
import org.eclipse.swt.widgets.Display;
/**
* Provides data to a trace.
*
* @author Xihui Chen
*
*/
public class CircularBufferDataProvider extends AbstractDataProvider {
public enum UpdateMode {
X_OR_Y("X or Y"), X_AND_Y("X AND Y"), X("X"), Y("Y"), TRIGGER("Trigger");
private UpdateMode(String description) {
this.description = description;
}
private String description;
@Override
public String toString() {
return description;
}
public static String[] stringValues() {
String[] sv = new String[values().length];
int i = 0;
for (UpdateMode p : values())
sv[i++] = p.toString();
return sv;
}
}
public enum PlotMode {
LAST_N("Plot last n pts."), N_STOP("Plot n pts & stop.");
private PlotMode(String description) {
this.description = description;
}
private String description;
@Override
public String toString() {
return description;
}
public static String[] stringValues() {
String[] sv = new String[values().length];
int i = 0;
for (PlotMode p : values())
sv[i++] = p.toString();
return sv;
}
}
private CircularBuffer<ISample> traceData;
private double currentXData;
private double currentYData;
private long currentYDataTimestamp;
private boolean currentXDataChanged = false;
private boolean currentYDataChanged = false;
// private boolean currentYDataTimestampChanged = false;
private double[] currentXDataArray = new double[] {};
private double[] currentYDataArray = new double[] {};
private boolean currentXDataArrayChanged = false;
private boolean currentYDataArrayChanged = false;
private boolean xAxisDateEnabled = false;
private int updateDelay = 0;
private boolean duringDelay = false;
private boolean concatenate_data = true;
/**
* this indicates if the max and min of the data need to be recalculated.
*/
protected boolean dataRangedirty = false;
private UpdateMode updateMode = UpdateMode.X_AND_Y;
private PlotMode plotMode = PlotMode.LAST_N;
private Runnable fireUpdate;
private int clippingWindow = -1;
public CircularBufferDataProvider(boolean chronological) {
super(chronological);
traceData = new CircularBuffer<ISample>(100);
fireUpdate = new Runnable() {
public void run() {
for (IDataProviderListener listener : listeners) {
listener.dataChanged(CircularBufferDataProvider.this);
}
duringDelay = false;
}
};
}
/**
* @param currentXData
* the currentXData to set
*/
public synchronized void setCurrentXData(double newValue) {
this.currentXData = newValue;
currentXDataChanged = true;
tryToAddDataPoint();
}
/**
* Set current YData. It will automatically make timestamp disabled.
*
* @param currentYData
* the currentYData to set
*/
public synchronized void setCurrentYData(double newValue) {
this.currentYData = newValue;
currentYDataChanged = true;
xAxisDateEnabled = false;
// if(!xAxisDateEnabled|| (xAxisDateEnabled &&
// currentYDataTimestampChanged))
tryToAddDataPoint();
}
public synchronized void addSample(ISample sample) {
if (traceData.size() == traceData.getBufferSize() && plotMode == PlotMode.N_STOP)
return;
traceData.add(sample);
fireDataChange();
}
/**
* Set the time stamp of currrent YData
*
* @param timestamp
* timestamp of Y data in milliseconds.
*/
public synchronized void setCurrentYDataTimestamp(long timestamp) {
if (!xAxisDateEnabled) {
clearTrace();
xAxisDateEnabled = true;
}
this.currentYDataTimestamp = timestamp;
// currentYDataTimestampChanged = true;
if (currentYDataChanged)
tryToAddDataPoint();
}
/**
* Set current YData and its timestamp when the new value generated.
*
* @param currentYData
* the currentYData to set
* @param timestamp
* timestamp of Y data in milliseconds.
*/
public synchronized void setCurrentYData(double newValue, long timestamp) {
xAxisDateEnabled = true;
this.currentYData = newValue;
currentYDataChanged = true;
this.currentYDataTimestamp = timestamp;
// currentYDataTimestampChanged = true;
tryToAddDataPoint();
}
/**
* Try to add a new data point to trace data. Whether it will be added or
* not is up to the update mode.
*/
private void tryToAddDataPoint() {
if (traceData.size() == traceData.getBufferSize() && plotMode == PlotMode.N_STOP)
return;
switch (updateMode) {
case X_OR_Y:
if ((chronological && currentYDataChanged)
|| (!chronological && (currentXDataChanged || currentYDataChanged)))
addDataPoint();
break;
case X_AND_Y:
if ((chronological && currentYDataChanged)
|| (!chronological && (currentXDataChanged && currentYDataChanged)))
addDataPoint();
break;
case X:
if ((chronological && currentYDataChanged) || (!chronological && currentXDataChanged))
addDataPoint();
break;
case Y:
if (currentYDataChanged)
addDataPoint();
break;
case TRIGGER:
default:
break;
}
}
/**
* add a new data point to trace data.
*/
private void addDataPoint() {
double newXValue;
if (!concatenate_data)
traceData.clear();
if (chronological) {
if (xAxisDateEnabled) {
if (updateMode != UpdateMode.TRIGGER)
newXValue = currentYDataTimestamp;
else
newXValue = Calendar.getInstance().getTimeInMillis();
} else {
if (traceData.size() == 0)
newXValue = 0;
else
newXValue = traceData.getTail().getXValue() + 1;
}
} else {
newXValue = currentXData;
}
traceData.add(new Sample(newXValue, currentYData));
currentXDataChanged = false;
currentYDataChanged = false;
// currentYDataTimestampChanged = false;
fireDataChange();
}
/**
* @param currentXData
* the currentXData to set
*/
public synchronized void setCurrentXDataArray(double[] newValue) {
this.currentXDataArray = newValue;
currentXDataArrayChanged = true;
tryToAddDataArray();
}
/**
* @param currentXData
* the currentXData to set
*/
public synchronized void setCurrentYDataArray(double[] newValue) {
this.currentYDataArray = newValue;
currentYDataArrayChanged = true;
tryToAddDataArray();
}
/**
* Try to add a new data array to trace data. Whether it will be added or
* not is up to the update mode.
*/
private void tryToAddDataArray() {
if (traceData.size() == traceData.getBufferSize() && plotMode == PlotMode.N_STOP)
return;
switch (updateMode) {
case X_OR_Y:
if ((chronological && currentYDataArrayChanged)
|| (!chronological && (currentXDataArrayChanged || currentYDataArrayChanged)))
addDataArray();
break;
case X_AND_Y:
if ((chronological && currentYDataArrayChanged)
|| (!chronological && (currentXDataArrayChanged && currentYDataArrayChanged)))
addDataArray();
break;
case X:
if ((chronological && currentYDataArrayChanged) || (!chronological && currentXDataArrayChanged))
addDataArray();
break;
case Y:
if (currentYDataArrayChanged)
addDataArray();
break;
case TRIGGER:
default:
break;
}
}
/**
* add a new data point to trace data.
*/
private void addDataArray() {
if (!concatenate_data)
traceData.clear();
if (chronological) {
double[] newXValueArray;
newXValueArray = new double[currentYDataArray.length];
if (traceData.size() == 0)
for (int i = 0; i < currentYDataArray.length; i++) {
newXValueArray[i] = i;
}
else
for (int i = 1; i < currentYDataArray.length + 1; i++) {
newXValueArray[i - 1] = traceData.getTail().getXValue() + i;
}
for (int i = 0; i < Math.min(traceData.getBufferSize(),
Math.min(newXValueArray.length, currentYDataArray.length)); i++) {
traceData.add(new Sample(newXValueArray[i], currentYDataArray[i]));
}
} else {
// newXValueArray = currentXDataArray;
// if the data array size is longer than buffer size,
// just ignore the tail data.
for (int i = 0; i < Math.min(traceData.getBufferSize(),
Math.min(currentXDataArray.length, currentYDataArray.length)); i++) {
traceData.add(new Sample(currentXDataArray[i], currentYDataArray[i]));
}
}
currentXDataArrayChanged = false;
currentYDataArrayChanged = false;
// currentYDataTimestampChanged = false;
fireDataChange();
}
/**
* Clear all data on in the data provider.
*/
public synchronized void clearTrace() {
traceData.clear();
currentXDataArray = new double[] {};
currentYDataArray = new double[] {};
currentXDataChanged = false;
currentYDataChanged = false;
currentXDataArrayChanged = false;
currentYDataArrayChanged = false;
fireDataChange();
}
public Iterator<ISample> iterator() {
return traceData.iterator();
}
/**
* @param bufferSize
* the bufferSize to set
*/
public synchronized void setBufferSize(int bufferSize) {
traceData.setBufferSize(bufferSize, false);
}
/**
* @param updateMode
* the updateMode to set
*/
public void setUpdateMode(UpdateMode updateMode) {
this.updateMode = updateMode;
}
/**
* @return the update mode.
*/
public UpdateMode getUpdateMode() {
return updateMode;
}
/**
* In TRIGGER update mode, the trace data could be updated by this method
*
* @param triggerValue
* the triggerValue to set
*/
public void triggerUpdate() {
// do not update if no new data was added, otherwise, it will add (0,0)
// which is not a real sample.
if (traceData.size() == 0 && !(currentYDataChanged || currentYDataArrayChanged))
return;
if (currentYDataArray.length > 0)
addDataArray();
else
addDataPoint();
}
@Override
protected void innerUpdate() {
dataRangedirty = true;
}
@Override
protected void updateDataRange() {
if (!dataRangedirty)
return;
dataRangedirty = false;
if (getSize() > 0) {
int lowerBound = 0;
if (getSize() > clippingWindow && clippingWindow > 0) {
lowerBound = (getSize() - 1) - clippingWindow;
}
double xMin;
double xMax;
xMin = getSample(lowerBound).getXValue();
xMax = xMin;
double yMin;
double yMax;
yMin = getSample(lowerBound).getYValue();
yMax = yMin;
for (int i = lowerBound + 1; i < getSize(); i++) {
ISample dp = getSample(i);
if (xMin > dp.getXValue() - dp.getXMinusError())
xMin = dp.getXValue() - dp.getXMinusError();
if (xMax < dp.getXValue() + dp.getXPlusError())
xMax = dp.getXValue() + dp.getXPlusError();
if (yMin > dp.getYValue() - dp.getYMinusError())
yMin = dp.getYValue() - dp.getYMinusError();
if (yMax < dp.getYValue() + dp.getYPlusError())
yMax = dp.getYValue() + dp.getYPlusError();
}
xDataMinMax = new Range(xMin, xMax);
yDataMinMax = new Range(yMin, yMax);
} else {
xDataMinMax = null;
yDataMinMax = null;
}
}
/**
* @param plotMode
* the plotMode to set
*/
public void setPlotMode(PlotMode plotMode) {
this.plotMode = plotMode;
}
@Override
public ISample getSample(int index) {
return traceData.getElement(index);
}
@Override
public int getSize() {
return traceData.size();
}
/**
* If xAxisDateEnable is true, you will need to use
* {@link #setCurrentYData(double, long)} or
* {@link #setCurrentYDataTimestamp(long)} to set the time stamp of ydata.
* This flag will be automatically enabled when either of these two methods
* were called. The default value is false.
*
* @param xAxisDateEnabled
* the xAxisDateEnabled to set
*/
public void setXAxisDateEnabled(boolean xAxisDateEnabled) {
this.xAxisDateEnabled = xAxisDateEnabled;
}
/**
* @param updateDelay
* Delay in milliseconds between plot updates. This may help to
* reduce CPU usage. The default value is 0ms.
*/
public synchronized void setUpdateDelay(int updateDelay) {
this.updateDelay = updateDelay;
}
@Override
protected synchronized void fireDataChange() {
if (updateDelay > 0) {
innerUpdate();
if (!duringDelay) {
Display.getCurrent().timerExec(updateDelay, fireUpdate);
duringDelay = true;
}
} else
super.fireDataChange();
}
public void setConcatenate_data(boolean concatenate_data) {
this.concatenate_data = concatenate_data;
}
public boolean isConcatenate_data() {
return concatenate_data;
}
public void setClippingWindow(int clippingWindow) {
assert clippingWindow >= 0;
assert clippingWindow <= getSize();
this.clippingWindow = clippingWindow;
}
public int getClippingWindow() {
return clippingWindow;
}
}