Moving / Moving Average in d3.js v4

this is a necessary update for this question asked for d3 v3.

as mentioned here https://github.com/d3/d3/blob/master/CHANGES.md#shapes-d3-shape

4.0 introduces a new curve API to determine how line and area shapes are interpolated between data points. The line.interpolate and area.interpolate methods have been replaced by line.curve and area.curve. Curves are implemented using the curve interface, and not as a function that returns an SVG path data string; this allows you to create curves for SVG or Canvas. In addition, line.curve and area.curve now perform a function that instantiates a curve for a given context, not a row.

this is an amazing amount of change and will interrupt all the previous examples unless I miss something extremely obvious. of course, the new line.curve documentation https://github.com/d3/d3-shape#line_curve is less useful:

line.curve ([curve]) <>

If a curve is specified, sets the factory curve and the generator returns this string. If no curve is specified, returns the current factory curve, which defaults to curveLinear.

I am very lost and there are no good examples. Can someone please update the above question ( Move / moving average position in d3.js ) for d3 v4?

Many thanks. I have spent too many hours on this already :(

+2
source share
2 answers

Another answer is really good and probably the best way to achieve your goals. But for posterity, here is n-moving-average in the new d3 form of the 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> 
+3
source

They actually use d3.v2, so perhaps their options were limited then. But on d3.v4, I don’t see the need for a custom factory curve when just calculating the moving average and plotting it with curveBasis gives you the exact result.

Below you can find a simplified and updated script for the question you mentioned.

 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> 
+4
source

All Articles