Skip to content

Instantly share code, notes, and snippets.

@armollica
Last active November 28, 2015 03:50
Show Gist options
  • Save armollica/14d432a338787f6b1a24 to your computer and use it in GitHub Desktop.
Save armollica/14d432a338787f6b1a24 to your computer and use it in GitHub Desktop.
Triangular Tessellation

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.

<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment