Here we use a series of sine waves to approximate a square wave.
See the Wikipedia entry on Fourier Analysis for more details.
Here we use a series of sine waves to approximate a square wave.
See the Wikipedia entry on Fourier Analysis for more details.
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<title>Fourier Approximation</title> | |
<script src="https://d3js.org/d3.v5.min.js"></script> | |
</head> | |
<body> | |
<div id="viz"> | |
</div> | |
<script src="index.js"></script> | |
</body> | |
</html> |
// Time-length (in milliseconds) before graph updates | |
const intervalLength = 1000 | |
// Number of fourier approximations before resetting | |
const numIterations = 15 | |
var margin = { top: 10, right: 30, bottom: 30, left: 60 }, | |
height = 500 - margin.top - margin.bottom; | |
var width = 1.618 * height - margin.left - margin.right | |
// append the svg object to the body of the page | |
var svg = d3.select("#viz") | |
.append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom) | |
.append("g") | |
.attr("transform", | |
"translate(" + margin.left + "," + margin.top + ")"); | |
// Create a linear space | |
// Start: xMin, Stop: xMax, n: numSteps | |
function linSpace(start, stop, n) { | |
var arr = []; | |
const step = (stop - start) / n | |
for (var loc = start; loc <= stop; loc = loc + step) { | |
arr.push(loc) | |
} | |
return arr; | |
} | |
var myDomain = linSpace(-2 * Math.PI, 2 * Math.PI, 1000) | |
// Scales and Axes | |
var x = d3.scaleLinear() | |
.domain(d3.extent(myDomain)) | |
.range([0, width]); | |
svg.append("g") | |
.attr("transform", `translate(0,${height / 2})`) | |
.call(d3.axisBottom(x).ticks(2)); | |
var y = d3.scaleLinear() | |
.domain([-1.1, 1.1]) | |
.range([height, 0]); | |
svg.append("g") | |
.attr("transform", `translate(${width / 2},0)`) | |
.call(d3.axisLeft(y).ticks(4)); | |
// ########################### | |
// construct the k-th approximation of the square wave | |
function approxSquare(k, x) { | |
if (k == 0) { | |
return Math.sin(x) | |
} else { | |
return Math.sin((2 * k + 1) * x) / (2 * k + 1) + approxSquare(k - 1, x) | |
} | |
} | |
function drawSquareWave() { | |
const myFunction = x => approxSquare(1000, x) | |
var myImage = myDomain.map(d => myFunction(d, 0)) | |
var data = d3.zip(myDomain, myImage) | |
svg.append("path") | |
.datum(data) | |
.attr("fill", "none") | |
.attr("stroke", "maroon") | |
.attr("stroke-width", 1.5) | |
.attr("d", d3.line() | |
.x(d => x(d[0])) | |
.y(d => y(d[1])) | |
) | |
.style("opacity", "0.5") | |
} | |
function drawFirstGraph() { | |
const myFunction = x => approxSquare(0, x) | |
var myImage = myDomain.map(d => myFunction(d, 0)) | |
var data = d3.zip(myDomain, myImage) | |
svg.append("path") | |
.datum(data) | |
.attr("fill", "none") | |
.attr("stroke", "steelblue") | |
.attr("stroke-width", 1.5) | |
.attr("id", "mainPath") | |
.attr("d", d3.line() | |
.x(d => x(d[0])) | |
.y(d => y(d[1])) | |
) | |
} | |
function updateGraph(k) { | |
const myFunction = x => approxSquare(k, x) | |
const myImage = myDomain.map(d => myFunction(d)) | |
const localData = d3.zip(myDomain, myImage) | |
const path = d3.selectAll("#mainPath").datum(localData) | |
path.transition() | |
.duration(intervalLength) | |
.attr("d", d3.line() | |
.x(d => x(d[0])) | |
.y(d => y(d[1])) | |
) | |
} | |
drawFirstGraph() | |
drawSquareWave() | |
var iteration = 0 | |
d3.interval(() => { | |
if (iteration < numIterations) { | |
iteration = iteration + 1 | |
updateGraph(iteration) | |
} | |
else { | |
iteration = 0 | |
updateGraph(iteration) | |
} | |
}, intervalLength) |