javascript canvas:用曲线绘制移动平均线

时间:2017-06-08 02:18:17

标签: javascript canvas stockquotes

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

enter image description here

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

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 个答案:

答案 0 :(得分:3)

要获得移动平均值,您需要在当前样本的任一侧获得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倍。

&#13;
&#13;
// 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;
&#13;
body { font-family : arial; }
.red { color : #F50; }
.blue { color : #0AF; }
.green { color : #4C0; }
canvas { position : absolute; top : 0px; left :130px; }
&#13;
<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>
&#13;
&#13;
&#13;