Curves.js

function MovingAverage(context, window) {
  this._context = context;
  this._points = { x: [], y: [] };
  this._window = window;
}

MovingAverage.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._window) {
      return;
    }

    const sum = (a, b) => a + b;
    const u = this._points.x.reduce(sum, 0) / this._window,
          v = this._points.y.reduce(sum, 0) / this._window;

    this._points.x.shift();
    this._points.y.shift();

    switch (this._point) {
      case 0:
        this._point = 1;
        this._line ? this._context.lineTo(u, v) : this._context.moveTo(u, v);
        break;
      case 1:
        this._point = 2;
        // proceed
      default:
        this._context.lineTo(u, v);
        break;
    }
  }
};

export function movingAverage() {
  let window = 1;

  function average(context) {
    return new MovingAverage(context, window);
  }

  average.window = function(_) {
    return arguments.length ? (window = Math.max(_, 1), average) : window;
  };

  return average;
}