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

时间:2016-12-29 19:31:47

标签: javascript d3.js

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

如此处所述https://github.com/d3/d3/blob/master/CHANGES.md#shapes-d3-shape

  

4.0引入了一个新的曲线API,用于指定线和区域形状在数据点之间进行插值的方式。 line.interpolate和area.interpolate方法已替换为line.curve和area.curve。使用曲线接口而不是作为返回SVG路径数据字符串的函数来实现曲线;这允许曲线渲染到SVG或Canvas。此外,line.curve和area.curve现在采用一个函数来实例化给定上下文的曲线,而不是字符串。

这是一个令人震惊的变化,并打破了所有先前的例子,除非我错过了一些非常明显的事情。当然,新的line.curve文档https://github.com/d3/d3-shape#line_curve不太有帮助:

  

line.curve([curve])<>

     

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

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

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

2 个答案:

答案 0 :(得分:5)

他们实际上正在使用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>
&#13;
&#13;
&#13;

答案 1 :(得分:3)

另一个答案非常好,可能最好的方式来实现您的目标。但是为了后人的缘故,这里是新的d3形 curve factory 方法中的n移动平均值:

&#13;
&#13;
<!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>
&#13;
&#13;
&#13;