Boids

Simulation
Code
chart = {
  const width = fullWidth,
        height = fullHeight;

  const window = d3.create("svg")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("viewBox", `0 0 ${fullWidth} ${fullHeight}`)
    .attr("preserveAspectRatio", "xMidYMid meet");

  const svg = window.append("g")
    .attr("class", "boids");

  let cohesionCoeff = defaults.cohesionCoeff,
      alignmentCoeff = defaults.alignmentCoeff,
      separationCoeff = defaults.separationCoeff;

  const n = 500,
        separationDistance = 30,
        neighborDistance = 60,
        maxVelocity = 2,
        maxAcceleration = 0.02;

  const line = d3.line();
  const color = d3.scaleSequential(d3.interpolateYlGnBu)
    .domain([0, maxVelocity]);

  const randomX = d3.randomUniform(0, width),
        randomY = d3.randomUniform(0, height),
        randomVx = d3.randomUniform(0, maxVelocity),
        randomVy = d3.randomNormal(0, maxVelocity / 4);

  let boids = d3.range(n).map(() => ({
    pos: new Vec(randomX(), randomY()),
    vel: new Vec(randomVx(), randomVy()),
    acc: new Vec()
  }));

  function tick(t) {
    boids = boids.filter(b =>
      (b.pos.x > 0) & (b.pos.x < width) &
      (b.pos.y > 0) & (b.pos.y < height));

    boids.push({
      pos: new Vec(0, randomY()),
      vel: new Vec(randomVx(), randomVy()),
      acc: new Vec()
    });

    boids.forEach(b1 => {
      const cohesionForce = new Vec(),
            alignmentForce = new Vec(),
            separationForce = new Vec();

      boids.forEach(b2 => {
        if (b1 === b2) {
          return;
        }

        const separation = b2.pos.clone().minus(b1.pos),
              distance = separation.length();

        if (distance < separationDistance) {
          separationForce.minus(separation);
        } else if (distance < neighborDistance) {
          cohesionForce.plus(separation);
          alignmentForce.plus(b2.vel.clone().minus(b1.vel));
        }
      });

      cohesionForce.normalize(cohesionCoeff);
      alignmentForce.normalize(alignmentCoeff);
      separationForce.normalize(separationCoeff);

      b1.acc = new Vec()
        .plus(cohesionForce)
        .plus(alignmentForce)
        .plus(separationForce)
        .truncate(maxAcceleration);
      b1.vel
        .plus(b1.acc)
        .truncate(maxVelocity);
      b1.pos
        .plus(b1.vel);
    });

    svg.selectAll(".boid")
      .data(boids)
      .join("path")
        .attr("class", "boid")
        .style("stroke-width", 2)
        .style("stroke", b => color(b.vel.length()))
        .attr("d", b => {
          const v = b.vel.clone().normalize(20);
          return line([
            [b.pos.x - v.x / 2, b.pos.y - v.y / 2],
            [b.pos.x + v.x / 2, b.pos.y + v.y / 2]
          ]);
        });
  }

  const timer = d3.interval(tick, 20);

  invalidation.then(() => {
    timer.stop();
  });

  return Object.assign(window.node(), {
    update(values) {
      cohesionCoeff = values.cohesionCoeff;
      alignmentCoeff = values.alignmentCoeff;
      separationCoeff = values.separationCoeff;
    },
  });
}

The Boids model can be used to simulate the flocking behavior of birds.

Resources

Tools