代码之家  ›  专栏  ›  技术社区  ›  swyx

在d3.js v4中绘制滚动/移动平均值

  •  4
  • swyx  · 技术社区  · 9 年前

    这是对此的必要更新 question 要求d3 v3。

    正如这里提到的 https://github.com/d3/d3/blob/master/CHANGES.md#shapes-d3-shape

    4.0引入了一个新的曲线API,用于指定直线和面积形状如何在数据点之间插值。线。插值和面积。插值方法已替换为直线。曲线和面积曲线。曲线是使用曲线接口实现的,而不是作为返回SVG路径数据字符串的函数;这允许曲线渲染为SVG或画布。此外,行。曲线和面积。曲线现在采用一个函数,它为给定的上下文而不是字符串实例化曲线。

    这是一个惊人的变化量,打破了之前所有的例子,除非我遗漏了一些非常明显的东西。当然,新的路线。曲线文件 https://github.com/d3/d3-shape#line_curve 不太有用:

    线曲线([curve])<>

    如果指定了曲线,则设置曲线工厂并返回该线 发电机如果未指定曲线,则返回当前曲线 工厂,默认为曲线。

    我非常迷茫,没有好的例子。任何人都可以更新上述问题吗( Plot rolling/moving average in d3.js )对于d3和v4?

    非常感谢。我已经在这上面浪费了太多时间了:(

    2 回复  |  直到 9 年前
        1
  •  7
  •   cvsguimaraes    9 年前

    他们实际上使用的是d3.v2,所以当时他们的选择可能是有限的。但是在d3.v4上,我不认为在简单计算移动平均值并用 curveBasis 给出了完全相同的结果。

    下面您可以找到您提到的问题的简化和更新的提琴。

    movingAvg = function (data, neighbors) {
      return data.map((val, idx, arr) => {
        let start = Math.max(0, idx - neighbors), end = idx + neighbors
        let subset = arr.slice(start, end + 1)
        let sum = subset.reduce((a,b) => a + b)
        return sum / subset.length
      })
    }
    
    var data = [3, 66, 2, 76, 5, 20, 1, 30, 50, 9, 80, 5, 7]
    var dataAvg = movingAvg(data, 1)
    
    console.log(data.length, data)
    console.log(dataAvg.length, dataAvg)
    
    var w = 20,
        h = 80
    
    var x = d3.scaleLinear()
        .domain([0, 1])
        .range([0, w])
        
    var y = d3.scaleLinear()
        .domain([0, 100])
        .rangeRound([h, 0])
    
    
    var straightLine = d3.line()
        .x((d,i) => x(i))
        .y(d => y(d))
        
    var curvedLine = d3.line()
        .x((d,i) => x(i))
        .y(d => y(d))
        .curve(d3.curveBasis)
    
    window.onload = function() {
    	var chart = d3.select("body").append("svg")
    		.attr("class", "chart")
    		.attr("width", w * data.length -1)
    		.attr("height", h + 200)
    
    	chart
    		.append('path')
    		.attr('class', 'avg')
    		.datum(dataAvg)
    		.attr('d', curvedLine)
    
    	chart
    		.append('path')
    		.datum(data)
    		.attr('d', straightLine)
    }
    path {
        stroke: black;
        fill: none;
    }
    .avg {
        stroke: #ff0000;    
    }
    <script src="https://d3js.org/d3.v4.min.js"></script>
        2
  •  6
  •   cvsguimaraes    9 年前

    另一个答案真的很好,可能是 最好的方法 实现你的目标。但为了子孙后代,这里有一个新d3形状的n移动平均值 curve factory 方法:

    <!DOCTYPE html>
    <html>
    
    <head>
      <script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
      <script>
        function NMoveAvg(context, N) {
          this._context = context;
          this._points = {
            x: [],
            y: []
          };
          this._N = N;
        }
    
        NMoveAvg.prototype = {
          areaStart: function() {
            this._line = 0;
          },
          areaEnd: function() {
            this._line = NaN;
          },
          lineStart: function() {
            this._point = 0;
          },
          lineEnd: function() {
            if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
            this._line = 1 - this._line;
          },
          point: function(x, y) {
            x = +x, y = +y;
    
            this._points.x.push(x);
            this._points.y.push(y);
    
            if (this._points.x.length < this._N) return;
    
            var aX = this._points.x.reduce(function(a, b) {
                return a + b;
              }, 0) / this._N,
              aY = this._points.y.reduce(function(a, b) {
                return a + b;
              }, 0) / this._N;
    
            this._points.x.shift();
            this._points.y.shift();
    
            switch (this._point) {
              case 0:
                this._point = 1;
                this._line ? this._context.lineTo(aX, aY) : this._context.moveTo(aX, aY);
                break;
              case 1:
                this._point = 2; // proceed
              default:
                this._context.lineTo(aX, aY);
                break;
            }
          }
        };
    
        var curveNMoveAge = (function custom(N) {
    
          function nMoveAge(context) {
            return new NMoveAvg(context, N);
          }
    
          nMoveAge.N = function(N) {
            return custom(+N);
          };
    
          return nMoveAge;
        })(0);
      </script>
    </head>
    
    <body>
      <script>
        var data = [3, 66, 2, 76, 5, 20, 1, 30, 50, 9, 80, 5, 7];
    
        var w = 500,
          h = 500;
    
        var x = d3.scaleLinear()
          .domain([0, 12])
          .range([0, w]);
    
        var y = d3.scaleLinear()
          .domain([0, 100])
          .range([0, h]);
    
        var svg = d3.select("body")
          .append("svg")
          .attr("width", w)
          .attr("height", h);
    
    
        var line = d3.line()
          .x(function(d, i) {
            return x(i);
          })
          .y(function(d, i) {
            return y(d);
          });
    
        svg.append("path")
          .datum(data)
          .attr("d", line.curve(curveNMoveAge.N(3)))
          .style("fill", "none")
          .style("stroke", "steelblue");
    
        svg.append("path")
          .datum(data)
          .attr("d", line.curve(d3.curveLinear))
          .style("fill", "none")
          .style("stroke", "black");
      </script>
    </body>
    
    </html>