block by armollica 14d432a338787f6b1a24

Triangular Tessellation

Full Screen

Tessellating equilateral triangles. Move mouse to adjust the size of the triangles. Click to invert the sizing.

Could be extended for two-dimensional binning, useful when there are a lot of data points. Other shapes that tesselate well are squares and hexagons.

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Trianglar Tessellation</title>
  </head>
  <body>
    <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script>
      var width = 960,
          height = 500,
          trianglesAcross = 25; // # of triangles on the top row

      var color = d3.scale.linear()
        .domain([0, 1])
        .range(["#1c9099", "#ece2f0"])
        .interpolate(d3.interpolateLab);

      var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

      svg.call(draw, function(d) { return proximity(d, width/2, height/2)});

      var inverse = false;
      svg
        .on("mousemove", mousemove)
        .on("click", click);

      function mousemove() {
        var mouse = d3.mouse(this)
        svg.call(draw, function(d) {
          return inverse ?
            inverseProximity(d, mouse[0], mouse[1]) :
            proximity(d, mouse[0], mouse[1]);
        });
      }

      function click() {
        var mouse = d3.mouse(this)
        inverse = !inverse;
        svg
          .on("mousemove", null)
          .call(draw, function(d) {
            return inverse ?
              inverseProximity(d, mouse[0], mouse[1]) :
              proximity(d, mouse[0], mouse[1]);
          }, 1000);

        // Hacky way of preventing "mousemove" from interrupting animation
        setTimeout(function() {
          svg.on("mousemove", mousemove);
        }, 1000);
      }

      function proximity(d, x, y) {
        var dist = 1 - Math.sqrt(Math.pow(d.cx - x, 2) + Math.pow(d.cy - y, 2))/width;
        return Math.pow(dist, 6);
      }

      function inverseProximity(d, x, y) {
        var dist = Math.sqrt(Math.pow(d.cx - x, 2) + Math.pow(d.cy - y, 2))/width;
        return Math.pow(dist, 2);
      }


      function draw(selection, area, duration) {
        if (duration === undefined) duration = 0;

        var data = createTriangleData(width, height, trianglesAcross, area);

        var triangles = selection.selectAll("path").data(data);

        triangles.enter().append("path");

        triangles
          .transition().duration(duration)
          .attr("d", trianglePath)
          .style("fill", function(d) { return color(d.p); });

        triangles.exit().remove();
      }

      function trianglePath(d) {
        var alpha = Math.sqrt(d.p * Math.pow(d.alpha, 2)),
            ri = alpha * Math.sqrt(3) / 6,
            rc = alpha / Math.sqrt(3);

        var points = null;
        if (d.pointing == "up") {
          points = [
            [d.cx - alpha/2, d.cy - ri],
            [d.cx, d.cy + rc],
            [d.cx + alpha/2, d.cy - ri]
          ];
        }
        else if (d.pointing == "down") {
          points = [
            [d.cx - alpha/2, d.cy + ri],
            [d.cx + alpha/2, d.cy + ri],
            [d.cx, d.cy - rc]
          ];
        }
        return d3.svg.line()(points);
      }

      function createTriangleData(width, height, trianglesAcross, area) {
        // Source of geometric definitions:
        // https://en.wikipedia.org/wiki/Equilateral_triangle
        var alpha = width/trianglesAcross,  // maximum side length
            ri = alpha * Math.sqrt(3) / 6,  // maximum radius of inscribing circle
            rc = alpha / Math.sqrt(3);      // maximum radius of circumscribing circle

        var data = [];

        // Upward pointing triangles
        for (var x = alpha/2; x <= (width + alpha); x += alpha) {
          for (var y = ri, i = 0; y <= (height + alpha); y += (ri+rc), i++) {
            data.push({
              cx: x - (i % 2 == 0 ? 0 : alpha/2),
              cy: y,
              pointing: "up",
              alpha: alpha
            });
          }
        }

        // Downward pointing triangles
        for (var x = 0; x <= (width + alpha); x += alpha) {
          for (var y = rc, i = 0; y <= (height + alpha); y += (ri+rc), i++) {
            data.push({
              cx: x - (i % 2 == 0 ? 0 : alpha/2),
              cy: y,
              pointing: "down",
              alpha: alpha
            });
          }
        }

        // p in [0, 1] maps the triangle's area from 0 to its maximum area
        data = data.map(function(d) { d.p = area(d); return d; });

        return data;
      }
    </script>
  </body>
</html>