/******************************************************************************* * 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; } }