我必须为wave创建动画。我需要控制波的速度取决于数据的可用性。是否有可能加快波浪速度。我正在使用帆布作为波浪。
提前致谢
小提琴:https://jsfiddle.net/Chaitanya_Kumar/6ztr0Lfh/
function animate() {
if (x > data.length - 1) {
return;
}
if (continueAnimation) {
requestAnimationFrame(animate);
}
if (x++ < panAtX) {
var temp = data[x];
var final = constant-(temp);
ctx.fillRect(x, final, 1, 1);
ctx.lineTo(x, final);
ctx.stroke();
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath(); // reset the path
for (var xx = 0; xx < panAtX; xx++) {
var y = data[x - panAtX + xx];
var final = constant - (y);
ctx.fillRect(xx, final, 1, 1);
ctx.lineTo(xx, final);
}
ctx.stroke();
}
}
答案 0 :(得分:2)
以下是数据采样的示例。它使用线性插值对数据源进行二次采样,并在滚动图形显示上显示该数据。
来自您的问题和小提琴的数据表明您有一个恒定的采样率间隔,并且您希望改变该数据的显示速率。这就是我在下面的演示中所做的。
关于演示
图表是数据的实时显示,其从左到右的速度取决于您调用样本函数的速率。
displayBuffer.readFrom(dataSource, dataSpeed, samplesPerFrame)
displayBuffer
是保存可显示数据的对象
dataSource
是数据来源,具有read
和seek
函数以及readPos
您寻找位置dataSource.seek(0.01);
向前移动0.01个数据样本然后读取数据dataSource.read();
和线性插值返回。
这使您可以加快或减慢来自源数据的数据流。
数据阅读器对象
//------------------------------------------------------------------------------
// data reader reads from a data source
const dataReader = {
readPos : 0,
seek(amount){ // moves read pos forward or back
if(this.data.length === 0){
this.readPos = 0;
return 0;
}
this.readPos += amount;
this.readPos = this.readPos < 0 ? 0 :this.readPos >= this.data.length ? this.data.length - 1 : this.readPos;
return this.readPos;
},
// this function reads the data at read pos. It is a linear interpolation of the
// data and does nor repressent what the actual data may be at fractional read positions
read(){
var fraction = this.readPos % 1;
var whole = Math.floor(this.readPos);
var v1 = this.data[Math.min(this.data.length-1,whole)];
var v2 = this.data[Math.min(this.data.length-1,whole + 1)];
return (v2 - v1) * fraction + v1;
},
}
可以通过添加到dataReader来调整演示。
如果您的数据采样率不规则,则需要为每个样本添加时间戳。然后添加timeSeek
函数,该函数类似于搜索,但使用时间样本之间的斜率来计算给定时间的读取位置。它需要从当前采样时间到下一个采样时间(在搜索方向上)对每个采样进行采样,从而使得寻找不确定所需的CPU周期成为可能。
以下是一个示例seekTime
,它通过readPos
参数找到timeShift
(来自上面的dataReader对象)。对象的readTime
和readPos
属性已更新,下一个read()
调用将返回dataSource.readTime
处的数据。
readTime : 0, // current seeked time
seekTime(timeShift){ // Example is forward seek only
if(this.timeStamps.length === 0){
this.readPos = 0;
return 0;
}
this.readTime += timeShift; // set new seeked time
var readPos = Math.floor(this.readPos);
// move read pos forward until at correct sample
while(this.timeStamps[readPos] > this.readTime &&
readPos++ < this.timeStamps.length);
// Warning you could be past end of buffer
// you will need to check and set seek time to the last
// timestamp value and exit. Code below the following line
// will crash if you dont vet here.
//if(readPos === this.timeStamps.length)
// now readPos points to the first timeStamp less than the needed
// time position. The next read position should be a time ahead of the
// needed time
var t1 = this.timeStamps[readPos]; // time befor seekTime
var t2 = this.timeStamps[readPos+1]; // time after seekTime
// warning divide by zero if data bad
var fraction = (this.readTime-t1)/(t2-t1)); // get the sub sample fractional location for required time.
this.readPos = readPos + fraction;
return this.readPos;
},
警告我省略了所有安全检查。你应该检查缓冲区结束,错误的时移值。如果带有时间戳的数据具有错误的样本,您将得到除以零,这将使dataReader从该点开始仅返回NaN并抛出任何读取。所以要求安全审查。
注意要使上述带时间戳的功能起作用,您需要确保每个数据样本都有相应的timeStamp。如果每个样本没有一对一的匹配时间戳,则上述代码将不起作用。
对dataDisplay
的更改很简单。只需更改函数中的搜索调用即可
dataDisplay.readFrom(dataSource,speed,samples)
到dataSource.seekTime(speed / samples)
speed
现在代表时间而不是样本。 (或者我只是用seek()
覆盖seekTime()
函数,如果我有时间戳)这允许dataDisplay
对象按原样处理timeStamped和常规间隔数据。
该示例对随机数据进行采样,并以可变速度和采样率显示。使用向右键设置显示速度。帧速率是60fps左右,但你可以将速度变量缩放到帧之间的时间。
var ctx = canvas.getContext("2d");
window.focus();
//==============================================================================
// the current data read speed
var dataSpeed = 1;
var samplesPerFrame = 1;
requestAnimationFrame(mainLoop); // start animation when code has been parsed and executed
//------------------------------------------------------------------------------
// data reader reads from a data source
const dataReader = {
readPos : 0,
seek(amount){ // moves read pos forward or back
if(this.data.length === 0){
this.readPos = 0;
return 0;
}
this.readPos += amount;
this.readPos = this.readPos < 0 ? 0 :this.readPos >= this.data.length ? this.data.length - 1 : this.readPos;
return this.readPos;
},
// this function reads the data at read pos. It is a linear interpolation of the
// data and does nor repressent what the actual data may be at fractional read positions
read(){
var fraction = this.readPos % 1;
var whole = Math.floor(this.readPos);
var v1 = this.data[Math.min(this.data.length-1,whole)];
var v2 = this.data[Math.min(this.data.length-1,whole + 1)];
return (v2 - v1) * fraction + v1;
},
}
//------------------------------------------------------------------------------
// Create a data source and add a dataReader to it
const dataSource = Object.assign({
data : [],
},dataReader
);
// fill the data source with random data
for(let i = 0; i < 100000; i++ ){
// because random data looks the same if sampled every 1000 or 1 unit I have added
// two waves to the data that will show up when sampling at high rates
var wave = Math.sin(i / 10000) * 0.5;
wave += Math.sin(i / 1000) * 0.5;
// high frequency data shift
var smallWave = Math.sin(i / 100) * (canvas.height / 5);
// get a gaussian distributed random value
dataSource.data[i] = Math.floor(smallWave + ((wave + Math.random()+Math.random()+Math.random()+Math.random()+Math.random()) / 5) * canvas.height);
}
//------------------------------------------------------------------------------
// Data displayer used to display a data source
const dataDisplay = {
writePos : 0,
width : 0,
color : "black",
lineWidth : 1,
// this function sets the display width which limits the data buffer
// when it is called all buffers are reset
setDisplayWidth(width){
this.data.length = 0;
this.width = width;
this.writePos = 0;
if(this.lastRead === undefined){
this.lastRead = {};
}
this.lastRead.mean = 0;
this.lastRead.max = 0;
this.lastRead.min = 0;
},
// this draws the buffered data scrolling from left to right
draw(){
var data = this.data; // to save my self from writing this a zillion times
const ch = canvas.height / 2;
if(data.length > 0){ // only if there is something to draw
ctx.beginPath();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.lineJoin = "round";
if(data.length < this.width){ // when buffer is first filling draw from start
ctx.moveTo(0, data[0])
for(var i = 1; i < data.length; i++){
ctx.lineTo(i, data[i])
}
}else{ // buffer is full and write position is chasing the tail end
ctx.moveTo(0, data[this.writePos])
for(var i = 1; i < data.length; i++){
ctx.lineTo(i, data[(this.writePos + i) % data.length]);
}
}
ctx.stroke();
}
},
// this reads data from a data source (that has dataReader functionality)
// Speed is in data units,
// samples is number of samples per buffer write.
// samples is only usefull if speed > 1 and lets you see the
// mean, min, and max of the data over the speed unit
// If speed < 1 and sample > 1 the data is just a linear interpolation
// so the lastRead statistics are meaningless (sort of)
readFrom(dataSource,speed,samples){ // samples must be a whole positive number
samples = Math.floor(samples);
var value = 0;
var dataRead;
var min;
var max;
for(var i = 0; i < samples; i ++){ // read samples
dataSource.seek(speed / samples); // seek to next sample
dataRead = dataSource.read(); // read the sample
if(i === 0){
min = dataRead;
max = dataRead;
}else{
min = Math.min(dataRead,min);
max = Math.min(dataRead,max);
}
value += dataRead;
}
// write the samples data and statistics.
this.lastRead.min = min;
this.lastRead.max = max;
this.lastRead.delta = value / samples - this.lastRead.mean;
this.lastRead.mean = value / samples;
this.data[this.writePos] = value / samples;
this.writePos += 1;
this.writePos %= this.width;
}
}
// display data buffer
var displayBuffer = Object.assign({ // this data is displayed at 1 pixel per frame
data : [], // but data is written into it at a variable speed
},
dataDisplay // add display functionality
);
//------------------------------------------------------------------------------
// for control
const keys = {
ArrowLeft : false,
ArrowRight : false,
ArrowUp : false,
ArrowDown : false,
}
function keyEvent(event){
if(keys[event.code] !== undefined){
event.preventDefault();
keys[event.code] = true;
}
}
addEventListener("keydown",keyEvent);
//------------------------------------------------------------------------------
function mainLoop(time){
ctx.clearRect(0,0,canvas.width,canvas.height);
if(canvas.width !== displayBuffer.width){
displayBuffer.setDisplayWidth(canvas.width);
}
displayBuffer.readFrom(dataSource,dataSpeed,samplesPerFrame);
displayBuffer.draw();
//-----------------------------------------------------------------------------
// rest is display UI and stuff like that
ctx.font = "16px verdana";
ctx.fillStyle = "black";
//var dataValue =displayBuffer.lastRead.mean.toFixed(2);
//var delta = displayBuffer.lastRead.delta.toFixed(4);
var readPos = dataSource.readPos.toFixed(4);
//if(displayBuffer.lastRead.delta > 0){ delta = "+" + delta }
// ctx.fillText("Data : " + dataValue + " ( " +delta +" )" ,4,18);
ctx.setTransform(0.9,0,0,0.89,4,18);
ctx.fillText("Speed : " + dataSpeed.toFixed(3) + ", Sample rate :" +samplesPerFrame + ", Read @ "+readPos ,0,0);
ctx.setTransform(0.7,0,0,0.7,4,32);
if(samplesPerFrame === 1){
ctx.fillText("Keyboard speed -left, +right Sample rate +up",0,0);
}else{
ctx.fillText("Keyboard speed -left, +right Sample rate -down, +up",0,0);
}
ctx.setTransform(1,0,0,1,0,0);
if(keys.ArrowLeft){
keys.ArrowLeft = false;
if(dataSpeed > 1){
dataSpeed -= 1;
}else{
dataSpeed *= 1/1.2;
}
}
if(keys.ArrowRight){
keys.ArrowRight = false;
if(dataSpeed >= 1){
dataSpeed += 1;
}else{
dataSpeed *= 1.2;
if(dataSpeed > 1){ dataSpeed = 1 }
}
}
if(keys.ArrowUp){
keys.ArrowUp = false;
samplesPerFrame += 1;
}
if(keys.ArrowDown){
keys.ArrowDown = false;
samplesPerFrame -= 1;
samplesPerFrame = samplesPerFrame < 1 ? 1 : samplesPerFrame;
}
requestAnimationFrame(mainLoop);
}
canvas {
border : 2px black solid;
}
<canvas id=canvas width=512 height=200></canvas>
以这种方式读取和显示数据既快速又简单。它很容易为数据源和显示数据添加网格标记和数据处理。演示(常规间隔数据)可以轻松处理在放大和缩小数据时显示大型数据源。请注意,对于timeStamped数据,上述seekTime函数不适用于大型数据集。您需要索引此类数据以获得更有效的搜索时间。