分享

Fourier series visualisation with d3.js.

 imelee 2016-03-15
Pierre Guilleminot’s Block 7524988
Updated December 28, 2015

Fourier series visualisation with d3.js.

From Wikipedia:

In mathematics, a Fourier series decomposes periodic functions or periodic signals into the sum of a (possibly infinite) set of simple oscillating functions, namely sines and cosines (or complex exponentials).

Use the bottom right form to change the visualized serie.

index.html#

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
body {
  font: 13px/13px "Helvetica Neue", Helvetica, Arial, sans-serif;
  margin: auto;
  position: relative;
  width: 960px;
}
form {
  position: absolute;
  bottom: 10px;
  right: 10px;
}
.hide {
  display: none;
}
.coeff .dot {
  fill: hsla(207, 63%, 27%, 0.2);
}
.coeff.last .dot {
  fill: hsla(207, 63%, 27%, 1.0);
}
.coeff .circle {
  fill: none;
  stroke: hsl(0, 0%, 70%);
}
.coeff.first .circle {
  fill: none;
  stroke: hsl(0, 0%, 30%);
}
.coeff.last .circle {
  display: none;
}
.graph {
  fill: none;
  stroke: steelblue;
  stroke-width: 3px;
}
.trace {
  fill: none;
  stroke: steelblue;
}
.proj {
  fill: none;
  stroke: #000;
}
.axis {
  stroke: hsl(0, 0%, 70%);
}
</style>
</head>
<body>
<form>
  <p>
    <select id="type">
      <option value="square">Square</option>
      <option value="triangle">Triangle</option>
      <option value="sawtooth">Sawtooth</option>
      <option value="fibonacci">Fibonacci</option>
      <option value="pulse">Pulse</option>
    </select>
    <input id="size" type="number" value="6" min="1" max="40" step="1">
  </p>
  <p><input id="freq" type="range" value="0.3" min="0.01" max="0.5" step="0.01"> <label>Speed</label></p>
</form>
<script src="http:///d3.v3.min.js"></script>
<script>
(function() {
  "use strict";

  var π = Math.PI
  var τ = 2 * Math.PI

  var types = {
    square: function(n) {
      return (((n + 1) % 2) ? 0 : 1) / n;
    },
    triangle: function(n) {
      if (!(n % 2)) return 0;
      return ((n % 4 === 1) ? 1 : -1) / (n * n);
    },
    sawtooth: function(n) {
      return ((n % 2) ? -1 : 1) / (n + 1);
    },
    fibonacci: function(n) {
      var fst = 0.01, sec = 0.01, add;
      for (var i = 0; i < n; i++) {
        add = fst + sec;
        fst = sec;
        sec = add;
      }
      return add;
    },
    pulse: function(n) {
      return 0.1;
    }
  };

  function FT(A, N, φ) {
    φ = φ || 0;
    return function(x) {
      var n = -1, y = 0;
      while (++n < N) {
        y += A[n] * Math.sin(τ * (n + 1) * x + φ);
      }
      return y;
    }
  }

  function once(fn) {
    var exec = false;
    return function() {
      if (exec) return;
      exec = true,
      fn && fn();
    };
  }

  var
    margin = {top: 40, right: 100, bottom: 40, left: 150},
    W = 960,
    H = 500,
    w = W - margin.left - margin.right,
    h = H - margin.top - margin.bottom,

    radius = 140,
    theta  = 0,
    xmax   = 1.5,
    rate   = 1 / 60,

    tDomain = d3.range(0, 1.1, 1 / 1000),   // trace domain
    gDomain = d3.range(0, xmax, xmax / 1000), // graph domain

    C = types.square, // coeffiecients
    L = 6,            // size
    F = 0.3,          // frequence

    yCirc = d3.scale.linear().domain([-1, 1]).range([h/2 + radius, h/2 - radius]),
    xCirc = d3.scale.linear().domain([-1, 1]).range([0, 2 * radius]),
    rAxis = d3.scale.linear().domain([0, 1]).range([0, radius]),
    xAxis = d3.scale.linear().range([radius, W - margin.left]),

    Fxy, fx, fy,

    draw, timer, data = [];

  var graph = d3.svg.line()
    .x(function(d) { return xAxis(d); })
    .y(function(d) { return yCirc(fy(theta - d)); });

  var proj = d3.svg.line()
    .x(function(d) { return xCirc(d.x); })
    .y(function(d) { return yCirc(d.y); });

  var trace = d3.svg.line()
    .x(function(d) { return xCirc(fx(d)); })
    .y(function(d) { return yCirc(fy(d)); });

  function gTransform(d) {
    return "translate(" + xCirc(d.x) + "," + yCirc(d.y) + ")";
  }

  function hTransform(d) {
    return "translate(" + xAxis(d.f) + "," + yCirc(0) + ")";
  }

  var svg = d3.select("body")
    .append("svg")
      .attr("width", W)
      .attr("height", H)

  svg.append("line")
    .attr("class", "axis")
    .attr("y1", margin.top + yCirc(0)).attr("x1", 0)
    .attr("y2", margin.top + yCirc(0)).attr("x2", W);

  svg.append("line")
    .attr("class", "axis")
    .attr("x1", margin.left + xCirc(0)).attr("y1", 0)
    .attr("x2", margin.left + xCirc(0)).attr("y2", H);

  var vis = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var gPath = vis.append("path").attr("class", "graph");
  var tPath = vis.append("path").attr("class", "trace");
  var pPath = vis.append("path").attr("class", "proj");

  function cache() {
    var A;
    if (typeof C === "function") {
      A = d3.range(1, L + 1).map(C);
    } else {
      A = C.slice(0, L);
    }

    fx = FT(A, L - 1, π/2);
    fy = FT(A, L - 1, 0),

    Fxy = A.map(function(a, i) {
      return { X: FT(A, i, π/2), Y: FT(A, i, 0), r: Math.abs(a) };
    });
  }

  function calc() {
    if (!Fxy) cache();
    Fxy.forEach(function(f, i) {
      var d = data[i] || (data[i] = {x:0,y:0,r:0});
      d.x = f.X(theta);
      d.y = f.Y(theta);
      d.r = f.r;
      d.f = i + 1;
    });
    data.length = Fxy.length;
    return data;
  }

  function coeff() {
    var co = vis.selectAll(".coeff").data(calc());

    // exit
    co.exit().remove();

    // enter
    var en = co.enter().append("g").attr("class", "coeff");

    en.append("circle").attr("class", "circle");
    en.append("circle").attr("class", "dot").attr("r", 3);

    // update
    co.classed("last",  function(d, i) { return i === L - 1; });
    co.classed("first", function(d, i) { return i === 0; });

    co.select(".circle").attr("r", function(d) { return rAxis(d.r); })

    return co;
  }

  function drawGraph() {
    xAxis.domain([0, xmax]);
    coeff().attr("transform", gTransform);
    var last = data[data.length - 1];
    pPath.attr("d", proj([last, {x:0,y:last.y}]));
    gPath.attr("d", graph(gDomain));
    tPath.attr("d", trace(tDomain));
  }

  function drawHisto() {
    xAxis.domain([1, L]);
    coeff().attr("transform", hTransform);
  }

  function toggle(callback) {
    var tran;
    tran = (draw === drawGraph) ? hTransform : gTransform;
    draw = (draw === drawGraph) ? drawHisto : drawGraph;
    coeff().transition()
      .duration(1000)
      .attr("transform", tran)
      .each("end", once(callback));
  }


  function toggleGraph() {
    xAxis.domain([0, xmax]);
    toggle(function() {
      pPath.classed("hide", false);
      gPath.classed("hide", false);
      tPath.classed("hide", false);
      play();
    });
  }

  function toggleHisto() {
    xAxis.domain([1, L]);
    pPath.classed("hide", true);
    gPath.classed("hide", true);
    tPath.classed("hide", true);
    pause(); toggle(drawHisto);
  }

  function play() {
    if (timer) return;
    (function loop() {
      drawGraph();
      theta += F * rate;
      timer = setTimeout(loop, rate * 1000);
    })();
  }

  function pause() {
    if (!timer) return;
    clearTimeout(timer);
    timer = null;
  }

  function redraw() {
    cache(); draw();
  }

  d3.select("svg").on("click", function() { (draw === drawHisto) ? toggleGraph() : toggleHisto(); });
  d3.select("#freq").on("change", function() { F = +this.value; redraw(); });
  d3.select("#size").on("change", function() { L = +this.value; redraw(); });
  d3.select("#type").on("change", function() { C = types[this.value]; redraw(); });

  draw = drawGraph;
  play();

})();
</script>
</body>

LICENSE

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约