block by dribnet 9243c66a8549b31e047c9ea322901149

Branched Random Walks

Full Screen

(click to reload)

A fork of Mike Bostock’s branched random walk. The code has been refactored to allow a pen up/down operation, model was paramaterized further, and three variants with slightly different model settings are shown.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<canvas width="960" height="500"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script language="javascript" type="text/javascript" src="focusedRandom.js"></script>
<script>

var canvas = document.querySelector("canvas"),
    context = canvas.getContext("2d"),
    width = canvas.width,
    height = canvas.height,
    tail_y = [0.2 * canvas.height, 0.5 * canvas.height, 0.8 * canvas.height],
    randomY = d3.randomNormal(0,1.5),
    randomPenToUp = d3.randomUniform(-15, 1),
    randomPenToDown = d3.randomUniform(-5, 1);

render();

canvas.onclick = render;

function render() {
  context.clearRect(0, 0, width, height);
  render_tail(0);
  render_tail(1);
  render_tail(2);
}

function render_tail(which_tail) {
  var length = Math.floor(focusedRandom(80, 1200, 2, 900));
  var tail_length = Math.floor(focusedRandom(60, 400, 3, 200));
  var tail_every = Math.floor(focusedRandom(1, 30, 4, 4));
  var color = d3.scaleSequential(d3.interpolateRainbow).domain([0, length]);
  var x_bias = 600 / length;
  var local_randomX = d3.randomNormal(x_bias,2);

  var x0 = width / 20,
      y0 = tail_y[which_tail],
      pen_up0 = false,
      tail_chunk = Math.floor(tail_length / 20),
      mainWalk = randomWalk([[x0, y0, pen_up0, null]], length, local_randomX);

  context.lineJoin = "round";
  context.lineCap = "round";
  context.lineWidth = 1.5;
  context.strokeStyle = "black";
  renderWalk(mainWalk);
  context.globalCompositeOperation = "multiply";
  context.lineWidth = 1;
  for (var i = tail_every; i < mainWalk.length; i += tail_every) {
    var branchHistory = mainWalk.slice(0, i+1)
    for (var j = 0; j < 1; ++j) {
      context.strokeStyle = color(i);
      branchCopy = branchHistory.slice();
      for (var k = 0, m = 20; k < m; ++k) {
        context.globalAlpha = (m - k - 1) / m;
        var pieceWalk = randomWalk(branchCopy, tail_chunk, local_randomX),
            pieceEnd = pieceWalk[pieceWalk.length - 1];
        renderWalk(pieceWalk);
        branchCopy.push(pieceEnd)
      }
      context.globalAlpha = 1;
    }
  }
}

function renderWalk(walk) {
  var i, n = walk.length;
  context.beginPath();
  context.moveTo(walk[0][0], walk[0][1]);
  for (i = 0; i < n; ++i) {
    // check for pen_up
    if(walk[i][2]) {
      context.moveTo(walk[i][0], walk[i][1]);
    }
    else {
      context.lineTo(walk[i][0], walk[i][1]);
    }
  }
  context.stroke();
}

function randomWalk(history, n, local_randomX) {
  var points, i, x, y, pen_up, randomPenFn;

  points = new Array(n);
  points[0] = history[history.length - 1];
  [x, y, pen_up] = points[0];
  for (i = 1; i < n; ++i) {
    randomPenFn = pen_up ? randomPenToDown : randomPenToUp;
    points[i] = [
      x += local_randomX(),
      y += randomY(),
      pen_up ^= (randomPenFn() > 0)
    ];
  }
  return points;
}

</script>

focusedRandom.js

/* this is my general purpose random function
 *
 * random numbers are always paramaterized between min and max.
 * however, the "focus" variable allows an interpolation between
 * a uniform and gaussian distribution (normal extends
 * "focus" standard deviations from center and a focus of 0
 * is a uniform distribution).
 */
function focusedRandom(min, max, focus, mean) {
  if(max === undefined) {
    max = min;
    min = 0;
  }
  if(focus === undefined) {
    focus = 1.0;
  }
  if(mean === undefined) {
    mean = (min + max) / 2.0;
  }
  if(focus == 0) {
    return d3.randomUniform(min, max)();
  }
  else if(focus < 0) {
    focus = -1 / focus;
  }
  sigma = (0.5 * (max - min)) / focus;
  val = d3.randomNormal(mean, sigma)();
  if (val > min && val < max) {
    return val;
  }
  return d3.randomUniform(min, max)();
}