block by tophtucker 45e8112e70ea9192c8d666dba6fe79fc

Diagonal

Full Screen

Someone just wanted to make a grid of squares animate in diagonally. This takes a series of numbers and maps them to rows and columns using triangular numbers. (It might’ve been more straightforward to just invert the Cantor pairing function.)

I added little fractions text, so it doubles as a visual proof of the countability of the rationals! And then I made two, so it doubles as a visual proof of the fact that the sum of consecutive triangular numbers is a square number!

Refresh for a different pair of consecutive triangle numbers.

See also:

index.html

<!DOCTYPE html>
<meta charset="utf-8">

<style>
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  position: relative;
  font-family: sans-serif;
}

div {
  background: black;
  border: 1px solid white;
  position: absolute;
  color: rgba(255,255,255,.5);
  font-size: 16px;
  text-align: center;
  overflow: hidden;
}
</style>

<body></body>

<script src="//d3js.org/d3.v4.0.0-alpha.35.min.js" charset="utf-8"></script>
<script>

var pageWidth = 960;
var gridSize = 960/Math.ceil(Math.random()*24);
var triangularNumber = (960/gridSize)-1;
var delay = 50;

// Render warm upper larger triangle
renderTriangle(
  d3.select('body').append('section'), 
  triangularNumber + 1,
  d3.scaleWarm().domain([0, getTriangularNumber(triangularNumber+1)*2]),
  function(d) { return d; }
);

// Render cool lower smaller triangle
renderTriangle(
  d3.select('body').append('section'), 
  triangularNumber,
  d3.scaleCool().domain([getTriangularNumber(triangularNumber)*2, 0]),
  function(d) { return triangularNumber - d; }
);

function renderTriangle(selection, triangularNumber, color, offsetter) {

  // offsetter just lets us choose whether to count from upper left or bottom right
  if(offsetter === undefined) offsetter = function(d) { return d; };
  if(color === undefined) color = d3.scaleRainbow();

  selection.selectAll('div')
      .data(d3.range(getTriangularNumber(triangularNumber)).map(getUpperTriangleCoordinates))
    .enter()
      .append('div')
      .style('width', gridSize+'px')
      .style('height', gridSize+'px')
      .style('background-color', function(d,i) { return color(i); })
      .style('font-size', gridSize/4+'px')
      .style('line-height', gridSize+'px')
      .style('left', function(d,n) { return gridSize * offsetter(d.column) + 'px'; })
      .style('top', function(d,n) { return gridSize * offsetter(d.row) + 'px'; })
      .style('opacity', 0)
      .text(function(d,n) { return (d.row + 1) + '/' + (d.column + 1); })
    .transition()
      .delay(function(d,n) { return n * delay; })
      .style('opacity', 1);
}

// https://en.wikipedia.org/wiki/Triangular_number
function getTriangularNumber(n) {
  return n * (n + 1) / 2;
}

function getTriangularNumberInverse(n) {
  return (1/2) * (-1 + Math.sqrt(8 * n + 1));
}

/*
  0 2 5
  1 4 
  3
*/
function getUpperTriangleCoordinates(n) {
  var i = Math.floor(getTriangularNumberInverse(n));
  return {
    n: n,
    row: i - (n - getTriangularNumber(i)),
    column: n - getTriangularNumber(i)
  }
}

// Below, an unused alternative!

/*
  0
  1 2
  3 4 5
*/
function getLowerTriangleCoordinates(n) {
  var i = Math.floor(getTriangularNumberInverse(n));
  return {
    n: n,
    row: i,
    column: n - getTriangularNumber(i)
  }
}

</script>