block by emeeks 9e96c5ae7d36c6c57b42084ddf9a67ec

Bonfire

Full Screen

d3-bboxCollide coupled with requestAnimationFrame and SVG filters to create a bonfire.

index.html

<html>
<head>
  <title>Bounding Box Collision</title>
  <meta charset="utf-8" />
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="collide.js"></script>
</head>
<style>
  svg {
    height: 500px;
    width: 500px;
    border: 1px solid lightgray;
  }
</style>
<body>

<div id="viz">
  <svg class="main">
  </svg>
</div>
</body>
  <footer>
<script>


  var filter = d3.select("svg").append("defs").append("filter").attr("id", "gooeyCodeFilter");
  filter.append("feGaussianBlur").attr("id", "gaussblurrer").attr("in", "SourceGraphic").attr("stdDeviation", 6).attr("color-interpolation-filters", "sRGB").attr("result", "blur");
  filter.append("feColorMatrix").attr("in", "blur").attr("mode", "matrix").attr("values", "1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 34 -7").attr("result", "gooey");

var start = 0

var randomPoints = d3.range(500).map(function (d,i) {
  return {value: Math.random(), key: i}
})

var currentTick = 500

var networkCenter = d3.forceCenter().x(250).y(250);

var forceX = d3.forceX(function (d) {return 100})
    .strength(0.03)

 var forceY = d3.forceY(function (d) {return d.value * 500})
    .strength(0.5)

var collide = d3.bboxCollide(function (d,i) {
    return [[-d.value * 20, -d.value * 10],[d.value * 20, d.value * 10]]
  })
  .strength(0.5)
  .iterations(1)

var color = d3.scaleOrdinal(["#8a2b57", "#b13e1f", "#aa8d0f", "#b67221"])

d3.select("svg.main")
  .append("g")
  .attr("class", "viz")
.style("filter", "url(#gooeyCodeFilter)")
function step(timestamp) {
  if (!start) start = timestamp;
  var progress = timestamp - start;
  particles(progress);
    window.requestAnimationFrame(step);
}

window.requestAnimationFrame(step);

function particles(progress) {

  var nodeEnter = d3.select("g.viz")
  .selectAll("g.node")
  .data(randomPoints, function (d) {return d.key})
  .enter()
  .append("g")
  .attr("class", "node")

  var nodeExit = d3.select("g.viz")
  .selectAll("g.node")
  .data(randomPoints, function (d) {return d.key})
  .exit()
  .remove()

  nodeEnter.append("rect")
    .attr("class", "base")
    .style("fill-opacity", 1)
    .style("fill", function (d, i) {return d3.color(color(d.key)).brighter(1)})

  var force = d3.forceSimulation(randomPoints)
    .velocityDecay(0.6)
    .force("center", networkCenter)
    .force("x", forceX)
    .force("y", forceY)
    .force("collide", collide)
    .on("tick", updateNetwork)
    .stop()

  force.tick()

    d3.selectAll("rect")
    .attr("width", function (d) {return d.value * 40})
    .attr("height", function (d) {return d.value * 20})
    .attr("x", function (d) {return d.x + -d.value * 20})
    .attr("y", function (d) {return d.y + -d.value * 10})

  randomPoints.forEach(function (d) {
    d.value = d.value - 0.005
  })

  randomPoints = randomPoints.filter(function (d) {return d.value > 0})

  randomPoints.push({value: 1.25, x: Math.random() * 300 + 100, y: 750, key: currentTick += 1})

  function updateNetwork() {
  }

}



</script>
  </footer>

</html>

collide.js

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-quadtree')) :
  typeof define === 'function' && define.amd ? define(['exports', 'd3-quadtree'], factory) :
  (factory((global.d3 = global.d3 || {}),global.d3));
}(this, function (exports,d3Quadtree) { 'use strict';

  function bboxCollide (bbox) {

    function x (d) {
      return d.x + d.vx;
    }

    function y (d) {
      return d.y + d.vy;
    }

    function constant (x) {
      return function () {
        return x;
      };
    }

    var nodes,
        boundingBoxes,
        strength = 1,
        iterations = 1;

        if (typeof bbox !== "function") {
          bbox = constant(bbox === null ? [[0,0][1,1]] : bbox)
        }

        function force () {
          var i,
              tree,
              node,
              xi,
              yi,
              bbi,
              nx1,
              ny1,
              nx2,
              ny2

              var cornerNodes = []
              nodes.forEach(function (d, i) {
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + (boundingBoxes[i][1][0] + boundingBoxes[i][0][0]) / 2, y: d.y + (boundingBoxes[i][0][1] + boundingBoxes[i][1][1]) / 2})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][0][0], y: d.y + boundingBoxes[i][0][1]})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][0][0], y: d.y + boundingBoxes[i][1][1]})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][1][0], y: d.y + boundingBoxes[i][0][1]})
                cornerNodes.push({node: d, vx: d.vx, vy: d.vy, x: d.x + boundingBoxes[i][1][0], y: d.y + boundingBoxes[i][1][1]})
              })
              var cn = cornerNodes.length

          for (var k = 0; k < iterations; ++k) {
            tree = d3Quadtree.quadtree(cornerNodes, x, y).visitAfter(prepareCorners);

            for (i = 0; i < cn; ++i) {
              var nodeI = ~~(i / 5);
              node = nodes[nodeI]
              bbi = boundingBoxes[nodeI]
              xi = node.x + node.vx
              yi = node.y + node.vy
              nx1 = xi + bbi[0][0]
              ny1 = yi + bbi[0][1]
              nx2 = xi + bbi[1][0]
              ny2 = yi + bbi[1][1]
              tree.visit(apply);
            }
          }

          function apply (quad, x0, y0, x1, y1) {
              var data = quad.data
              if (data) {
                var bWidth = bbLength(bbi, 0),
                bHeight = bbLength(bbi, 1);

                if (data.node.index !== nodeI) {
                  var dataNode = data.node
                  var bbj = boundingBoxes[dataNode.index],
                    dnx1 = dataNode.x + dataNode.vx + bbj[0][0],
                    dny1 = dataNode.y + dataNode.vy + bbj[0][1],
                    dnx2 = dataNode.x + dataNode.vx + bbj[1][0],
                    dny2 = dataNode.y + dataNode.vy + bbj[1][1],
                    dWidth = bbLength(bbj, 0),
                    dHeight = bbLength(bbj, 1)

                  if (nx1 <= dnx2 && dnx1 <= nx2 && ny1 <= dny2 && dny1 <= ny2) {

                    var xSize = [Math.min.apply(null, [dnx1, dnx2, nx1, nx2]), Math.max.apply(null, [dnx1, dnx2, nx1, nx2])]
                    var ySize = [Math.min.apply(null, [dny1, dny2, ny1, ny2]), Math.max.apply(null, [dny1, dny2, ny1, ny2])]

                    var xOverlap = bWidth + dWidth - (xSize[1] - xSize[0])
                    var yOverlap = bHeight + dHeight - (ySize[1] - ySize[0])

                    var xBPush = xOverlap * strength * (yOverlap / bHeight)
                    var yBPush = yOverlap * strength * (xOverlap / bWidth)

                    var xDPush = xOverlap * strength * (yOverlap / dHeight)
                    var yDPush = yOverlap * strength * (xOverlap / dWidth)

                    if ((nx1 + nx2) / 2 < (dnx1 + dnx2) / 2) {
                      node.vx -= xBPush
                      dataNode.vx += xDPush
                    }
                    else {
                      node.vx += xBPush
                      dataNode.vx -= xDPush
                    }
                    if ((ny1 + ny2) / 2 < (dny1 + dny2) / 2) {
                      node.vy -= yBPush
                      dataNode.vy += yDPush
                    }
                    else {
                      node.vy += yBPush
                      dataNode.vy -= yDPush
                    }
                  }

                }
                return;
              }

              return x0 > nx2 || x1 < nx1 || y0 > ny2 || y1 < ny1;
          }

        }

        function prepareCorners (quad) {

          if (quad.data) {
            return quad.bb = boundingBoxes[quad.data.node.index]
          }
            quad.bb = [[0,0],[0,0]]
            for (var i = 0; i < 4; ++i) {
              if (quad[i] && quad[i].bb[0][0] < quad.bb[0][0]) {
                quad.bb[0][0] = quad[i].bb[0][0]
              }
              if (quad[i] && quad[i].bb[0][1] < quad.bb[0][1]) {
                quad.bb[0][1] = quad[i].bb[0][1]
              }
              if (quad[i] && quad[i].bb[1][0] > quad.bb[1][0]) {
                quad.bb[1][0] = quad[i].bb[1][0]
              }
              if (quad[i] && quad[i].bb[1][1] > quad.bb[1][1]) {
                quad.bb[1][1] = quad[i].bb[1][1]
              }
          }
        }

        function bbLength (bbox, heightWidth) {
          return bbox[1][heightWidth] - bbox[0][heightWidth]
        }

        force.initialize = function (_) {
          var i, n = (nodes = _).length; boundingBoxes = new Array(n);
          for (i = 0; i < n; ++i) boundingBoxes[i] = bbox(nodes[i], i, nodes);
        };

        force.iterations = function (_) {
          return arguments.length ? (iterations = +_, force) : iterations;
        };

        force.strength = function (_) {
          return arguments.length ? (strength = +_, force) : strength;
        };

        force.bbox = function (_) {
          return arguments.length ? (bbox = typeof _ === "function" ? _ : constant(+_), force) : bbox;
        };

        return force;
  }

  exports.bboxCollide = bboxCollide;

  Object.defineProperty(exports, '__esModule', { value: true });

}));