block by alexmacy c5d36ac67eee2ca7b69824f74fb501d5

Compound Radial Waves

Full Screen

This is the second in a series exploring how to calculate and display the combinations of multiple waves. See the previous block Compound Waves. This requires finding the least common multiple of the frequencies, which can get pretty heavy to process. This version uses adds a radial line to display the compound wave. This is very much restricted by the capabilities of SVG, which is why this one only has inputs for two frequencies - but the transitions are kinda nice…

More to come!

index.html

<!DOCTYPE html>
<html>
<head>
<style>
  body {
    background-color: black;
    color: rgb(200, 200, 200);
    margin: 0px;
  }
  path {
    fill: none;
  }
  table {
    margin-left: 10px;
  }
</style>
<script src="//d3js.org/d3.v4.min.js"></script>
</head>
<body>
  <svg></svg>
  <table>
    <tr>
      <td>freq. #1: <text id="f1"></text></td>
      <td>freq. #2: <text id="f2"></text></td>
      <td>Compound wavelength</td>
    </tr>
    <tr>
      <td><input type="range" min="1" max="100" value="12" oninput="draw()"></td>
      <td><input type="range" min="1" max="100" value="50" oninput="draw()"></td>
      <th id="LCM"></th>
    </tr>
  </table>
</body>
<script>
const width = Math.max(960, innerWidth),
    height = Math.max(500, innerHeight - 100);

const svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height)

const x = d3.scaleLinear().range([0, width]);
const angle = d3.scaleLinear().range([0, Math.PI * 2]);

const wave = d3.line()
    .x(function(d, i) {return x(i)})
    .y(function(d) {return d * 100})

const radial = d3.radialLine()
    .angle(function(d, i) {return angle(i)})
    .radius(function(d) {return d * 150});

const indivWaves = svg.selectAll("path")
    .data([[],[]])
  .enter().append("path")
    .style("stroke", "white")
    .style("stroke-width", .2)
    .attr("transform", "translate(0, " + 10 + ")");

const compoundWave = svg.append("path")
    .style("stroke", "red")
    .style("stroke-width", 2)
    .attr("transform", "translate(0, " + 10 + ")");

const LCMWave = svg.append("path")
    .style("stroke", "green")
    .style("stroke-width", 2)
    .attr("transform", "translate(" + width/2 + ", " + (height/2 + 40) + ")");

draw();

function draw() {
  const fVals = d3.selectAll("input").nodes().map(function(d) {return +d.value});
  const commonLength = LCM(fVals[0], fVals[1]) * 2 + 1;

  x.domain([0, commonLength]);
  angle.domain([0, commonLength - 1]);

  const wavesArray = fVals.map(function(d) { return []})
  const compoundArray = [];

  for (let i=0; i<commonLength; i++) {
    let combinedVal = 0;
    for (let n=0; n<wavesArray.length; n++) {
      const d = fVals[n]
      const thisVal = d3.easeSinInOut((i % (d*2) - d) / d);
      combinedVal += thisVal
      wavesArray[n].push(thisVal)
    }
    compoundArray.push(combinedVal/2)
  }

  indivWaves.data(wavesArray).attr("d", wave)
  compoundWave.datum(compoundArray).attr("d", wave)

  LCMWave.attr("d", "M" + transitionPoints(commonLength).join("L") + "Z")
    .datum(compoundArray)
      .interrupt()
      .transition()
      .attr("d", radial)

  d3.select("#f1").html(fVals[0])
  d3.select("#f2").html(fVals[1])
  d3.select("#LCM").html(commonLength)
}

function transitionPoints(newCount) {
  const totalLength = LCMWave.node().getTotalLength();
  const newPoints = []
  
  for (let i=0; i<newCount; i++) {
    const thisPoint = LCMWave.node().getPointAtLength((i/newCount)*totalLength) 
    newPoints.push([thisPoint.x, thisPoint.y])
  }
  return newPoints
}

function LCM(a, b) {
  return (a/gcd(a, b)*b)
}

function gcd(a,b) {
  while (true) {
    if (b == 0) return a;
    a %= b;
    if (a == 0) return b;
    b %= a;
  }
}
</script>
</html>