/******************************************************************************* * Copyright (c) 2010 Oak Ridge National Laboratory. * 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 ******************************************************************************/ package org.csstudio.swt.xygraph.dataprovider; import java.util.Calendar; import java.util.Iterator; import org.csstudio.swt.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. */ private boolean dataRangedirty = false; private UpdateMode updateMode = UpdateMode.X_AND_Y; private PlotMode plotMode = PlotMode.LAST_N; private Runnable fireUpdate; public CircularBufferDataProvider(boolean chronological) { super(chronological); traceData = new CircularBuffer<ISample>(100); fireUpdate = new Runnable(){ @Override 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. * @param currentYData the currentYData to set */ public synchronized void setCurrentYData(double newValue) { this.currentYData = newValue; currentYDataChanged = true; if(!xAxisDateEnabled|| (xAxisDateEnabled && currentYDataTimestampChanged)) tryToAddDataPoint(); } public synchronized void addSample(ISample sample){ if(traceData.size() == traceData.getBufferSize() && plotMode == PlotMode.N_STOP) return; traceData.add(sample); if (!hasErrors && sample.getYPlusError()>0d) { hasErrors= true; } fireDataChange(); } private boolean hasErrors = false; @Override public boolean hasErrors() { return hasErrors; } /**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(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])); } } currentXDataChanged = false; currentYDataChanged = false; currentYDataTimestampChanged = false; fireDataChange(); } public synchronized void clearTrace(){ traceData.clear(); 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){ double xMin; double xMax; xMin = traceData.getHead().getXValue(); xMax = xMin; double yMin; double yMax; yMin = traceData.getHead().getYValue(); yMax = yMin; for(ISample dp : traceData){ 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; } }