繁体   English   中英

javascript画布:用曲线绘制移动平均线

[英]javascript canvas: draw moving average line with curves

因此,基本上,我想在时间序列折线图的一定数量的点上绘制一条弯曲的平均线。 像这样:

在此处输入图片说明

我希望它跨越图表的整个长度,但是我无法弄清楚如何计算起点和终点,因为(我认为)平均值将是每个部分中间的一个点。 查看带有移动平均线的股票图表,您可以看到我想要实现的目标:

在此处输入图片说明

我首先根据一段时间将数据数组分成多个块,以计算平均值。 所以,如果我开始:

[
    { 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();

你会怎么做?

要获得移动平均值,您只需获取当前样本两侧的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> 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM