简体   繁体   English

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

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

So basically, I want to draw a curved average line over a certain amount of points of a time-series line chart. 因此,基本上,我想在时间序列折线图的一定数量的点上绘制一条弯曲的平均线。 Like this: 像这样:

在此处输入图片说明

I want it to span the entire length of the chart but I can't figure out how to calculate the start and end points because the average would (I think) be a point in the middle of each section. 我希望它跨越图表的整个长度,但是我无法弄清楚如何计算起点和终点,因为(我认为)平均值将是每个部分中间的一个点。 Looking at a stock chart with moving average you can see what I want to acheive: 查看带有移动平均线的股票图表,您可以看到我想要实现的目标:

在此处输入图片说明

I calculate the averages first by splitting the data array up into chunks based on a period of time. 我首先根据一段时间将数据数组分成多个块,以计算平均值。 So if I start with: 所以,如果我开始:

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

I get to: 我到达:

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

This is what I've tried where I end up with an incomplete line, one that doesnt start at the beginning of the chart and doesnt stop at the end, but stars and ends inside the chart at the first average time: 这是我尝试过以不完整的行结束的方法,该行不从图表的开头开始,也不在图表的结尾处停止,而是在图表的第一个平均时间加星号和结尾:

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

How would you do this? 你会怎么做?

To get a moving mean you need to just get the mean of n points either side of the current sample. 要获得移动平均值,您只需获取当前样本两侧的n点平均值即可。

For example 例如

// 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);
}

This method does not pull the mean forward giving the most accurate mean for each data point. 此方法不会将均值向前拉,不会为每个数据点提供最准确的均值。

The problem with this method is that you do not get a mean for the first n and last n samples, where n is the number of samples either side of the mean. 此方法的问题在于,您没有获得前n个和后n个样本的均值,其中n是均值两侧的样本数。

You can do an alternative that will pull the mean forward a little but by applying a weighted mean you can reduce the bias a little 您可以选择另一种方法,将均值向前拉一点,但通过应用加权均值,可以稍微降低偏差

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;
}

This method keeps that mean closer to the current sample end. 这种方法使该平均值更接近当前样本末端。

The example show a random data set and the two types of means plotted over it. 该示例显示了一个随机数据集,并在其上绘制了两种均值。 Click to get a new plot. 单击以获取新图。 The red line is the moving mean and the blue is the weighted mean. 红线是移动均值,蓝色线是加权均值。 Note how the blue line tends to follow the data a little slow. 请注意,蓝线趋向于跟随数据有点慢。 The green line is a weighted mean that has a sample range 4 times greater than the other two. 绿线是加权平均值,其样本范围是其他两个样本的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