2017-06-08 65 views
0

所以基本上,我想在时间序列折线图的一定数量的点上画曲线平均线。就像这样:javascript canvas:用曲线绘制移动平均线

enter image description here

我希望它跨越图表的整个长度,但我无法弄清楚如何计算的起点和终点,因为平均会(我认为)是一个点每个部分的中间。看着股票图表与移动平均线,你可以看到我想要什么acheive:

enter image description here

我通过拆分数据阵列成基于一段时间块先计算平均值。所以,如果我开始:

[ 
    { time: 1, value: 2 }, 
    { time: 2, value: 4 }, 
    { time: 3, value: 5 }, 
    { time: 4, value: 7 }, 
] 

我到:

var averages = [ 
    { 
     x: 1.5, 
     y: 3, 
    }, 
    { 
     x: 3.5 (the average time) 
     y: 6 (the average value) 
    }, 
] 

这是我已经试过,我结束了一个不完整的线,一个这并不在图表的起点开始并没有停在最后,但明星和第一次平均时间在图表内:

  ctx.moveTo((averages[0].x), averages[0].y); 

      for(var i = 0; i < averages.length-1; i ++) 
      { 

       var x_mid = (averages[i].x + averages[i+1].x)/2; 
       var y_mid = (averages[i].y + averages[i+1].y)/2; 
       var cp_x1 = (x_mid + averages[i].x)/2; 
       var cp_x2 = (x_mid + averages[i+1].x)/2; 
       ctx.quadraticCurveTo(cp_x1, averages[i].y ,x_mid, y_mid); 
       ctx.quadraticCurveTo(cp_x2, averages[i+1].y ,averages[i+1].x, averages[i+1].y); 
      } 

      ctx.stroke(); 

你会怎么做到这一点?

回答

1

要获得移动平均值,您只需要获取当前样本任意一侧的n个点的平均值。

例如

// array of data points 
const movingMean = []; // the resulting means 
const data = [12,345,123,53,134,...,219]; // data with index representing x axis 
const sampleSize = 5; 
for(var i = sampleSize; i < data.length - sampleSize; i++){ 
    var total = 0; 
    for(var j = i- sampleSize; j < i + sampleSize; j++){ 
     total += data[j]; 
    } 
    movingMean[i] = total/(sampleSize * 2); 
} 

这种方法不拉的平均正向给每个数据点最准确的意思。

该方法的问题在于,您没有得到第n个和最后n个样本的均值,其中n是均值两侧的样本数。

你可以做,这将拉动平均前进一点点,但通过应用加权平均可以减少偏差一点点

for(var i = sampleSize; i < data.length + Math.floor(sampleSize/4); i++){ 
    var total = 0; 
    var count = 0; 
    for(var j = sampleSize; j > 0; j --){ 
     var index = i - (sampleSize - j); 
     if(index < data.length){ 
      total += data[index] * j; // linear weighting 
      count += j; 
     } 
    } 
    movingMean[i-Math.floor(sampleSize/4)] = total/count; 
} 

这种方法保持意味着更接近当前采样结束的替代品。

该示例显示了一个随机数据集和绘制在其上的两种类型的手段。点击获得新的情节。红线是移动平均值,蓝色是加权平均值。注意蓝线如何趋于跟随数据缓慢一点。 绿线是一个加权平均值,其样本范围比其他两个大4倍。

// helper functions 
 
const doFor = (count, callback) => {var i = 0; while (i < count) { callback(i ++) } }; 
 
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a }; 
 
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min; 
 
const randG = (dis, min, max) => {var r = 0; doFor(dis,()=>r+=rand(min,max)); return r/dis}; 
 
function getMinMax(data){ 
 
    var min = data[0]; 
 
    var max = data[0]; 
 
    doFor(data.length - 1, i => { 
 
     min = Math.min(min,data[i+1]); 
 
     max = Math.max(max,data[i+1]); 
 
    }); 
 
    var range = max-min; 
 
    return {min,max,range}; 
 
} 
 
function plotData(data,minMax){ 
 
    ctx.beginPath(); 
 
    for(var i = 0; i < data.length; i++){ 
 
     if(data[i] !== undefined){ 
 
      var y = (data[i] - minMax.min)/minMax.range; 
 
      y = y *(ctx.canvas.height - 2) + 1; 
 
      ctx.lineTo(i/2,y); 
 
     } 
 
    } 
 
    ctx.stroke(); 
 
} 
 
function getMovingMean(data,sampleSize){ 
 
    const movingMean = []; // the resulting means 
 
    for(var i = sampleSize; i < data.length - sampleSize; i++){ 
 
     var total = 0; 
 
     for(var j = i- sampleSize; j < i + sampleSize; j++){ 
 
      total += data[j]; 
 
     } 
 
     movingMean[i] = total/(sampleSize * 2); 
 
    } 
 
    return movingMean[i]; 
 
} 
 

 
function getMovingMean(data,sampleSize){ 
 
    const movingMean = []; // the resulting means 
 
    for(var i = sampleSize; i < data.length - sampleSize; i++){ 
 
     var total = 0; 
 
     for(var j = i- sampleSize; j < i + sampleSize; j++){ 
 
      total += data[j]; 
 
     } 
 
     movingMean[i] = total/(sampleSize * 2); 
 
    } 
 
    return movingMean; 
 
} 
 

 
function getWeightedMean(data,sampleSize){ 
 
    const weightedMean = []; 
 
    for(var i = sampleSize; i < data.length+Math.floor(sampleSize/4); i++){ 
 
     var total = 0; 
 
     var count = 0; 
 
     for(var j = sampleSize; j > 0; j --){ 
 
      var index = i - (sampleSize - j); 
 
      if(index < data.length){ 
 
       total += data[index] * j; // linear weighting 
 
       count += j; 
 
      } 
 

 
     } 
 
     weightedMean[i-Math.floor(sampleSize/4)] = total/count; 
 
    } 
 
    return weightedMean; 
 
} 
 
const dataSize = 1000; 
 
const sampleSize = 50; 
 
canvas.width = dataSize/2; 
 
canvas.height = 200; 
 
const ctx = canvas.getContext("2d"); 
 
function displayData(){ 
 
    ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); 
 
    var dataPoint = 100; 
 
    var distribution = Math.floor(rand(1,8)); 
 
    var movement = rand(2,20); 
 
    const data = setOf(dataSize,i => dataPoint += randG(distribution, -movement, movement)); 
 
    const movingMean = getMovingMean(data, sampleSize); 
 
    const weightedMean = getWeightedMean(data, sampleSize*2); 
 
    const weightedMean1 = getWeightedMean(data, sampleSize*8); 
 
    var minMax = getMinMax(data); 
 
    ctx.strokeStyle = "#ccc"; 
 
    plotData(data,minMax); 
 
    ctx.strokeStyle = "#F50"; 
 
    plotData(movingMean,minMax); 
 
    ctx.strokeStyle = "#08F"; 
 
    plotData(weightedMean,minMax); 
 
    ctx.strokeStyle = "#4C0"; 
 
    plotData(weightedMean1,minMax); 
 
} 
 
displayData(); 
 
document.onclick = displayData;
body { font-family : arial; } 
 
.red { color : #F50; } 
 
.blue { color : #0AF; } 
 
.green { color : #4C0; } 
 
canvas { position : absolute; top : 0px; left :130px; }
<canvas id="canvas"></canvas> 
 
<div class="red">Moving mean</div> 
 
<div class="blue">Weighted mean</div> 
 
<div class="green">Wide weighted mean</div> 
 
<div>Click for another sample</div>