Skip to content

Instantly share code, notes, and snippets.

@mfilippo
Last active February 20, 2018 10:34
Show Gist options
  • Save mfilippo/26c47af10022bbad1396b8170fd1f1d6 to your computer and use it in GitHub Desktop.
Save mfilippo/26c47af10022bbad1396b8170fd1f1d6 to your computer and use it in GitHub Desktop.
Triangular Binning (Color)
license: gpl-3.0

This example shows how to use the d3-tribin plugin for triangular binning. 2,000 random points with a normal distribution are binned into hexagons; color encodes the number of points that fall into each bin. You can also use area encoding. Inspired by d3-hexbin by Mike Bostock.

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.d3 = global.d3 || {})));
}(this, function (exports) { 'use strict';
var sqrt3 = Math.sqrt(3);
var thirdPi = Math.PI / 3;
var angles = [0, thirdPi * 2, thirdPi * 4];
function pointX(d) {
return d[0];
}
function pointY(d) {
return d[1];
}
function tribin() {
var s,
inR,
outR,
h,
x = pointX,
y = pointY;
function tribin(points) {
var binsById = {},
bins = [],
i,
n = points.length;
for (i = 0; i < n; ++i) {
if (isNaN(px = +x.call(null, point = points[i], i, points)) || isNaN(py = +y.call(null, point, i, points))) continue;
var point,
px,
py,
row = Math.floor(py / h),
rowIsOdd = row % 2 == 1,
col = (rowIsOdd ? Math.floor((px + s / 2) / s) : Math.floor(px / s)),
offY = row * h,
offX = (rowIsOdd ? col * s - s / 2 : col * s),
relY = py - offY,
relX = px - offX,
binX = offX + s / 2,
binY = offY + outR,
binRotation = 0,
triangleCol = col * 2 + 1,
triangleRow = row,
m = h / (s / 2);
if (relY < (-m * relX) + h) {
triangleCol--;
binX = offX;
binY = offY + inR;
binRotation = Math.PI;
} else if (relY < (m * relX) - h) {
triangleCol++;
binX = offX + s;
binY = offY + inR;
binRotation = Math.PI;
}
var id = triangleRow + "-" + triangleCol,
bin = binsById[id];
if (bin) bin.push(point);
else {
bins.push(bin = binsById[id] = [point]);
bin.x = binX;
bin.y = binY;
bin.rotation = binRotation;
}
}
return bins;
}
function triangle(side, rotation) {
var x0 = 0, y0 = 0, r = side * sqrt3 / 3;
return angles.map(function(angle) {
var x1 = Math.sin(angle + rotation) * r,
y1 = -Math.cos(angle + rotation) * r,
dx = x1 - x0,
dy = y1 - y0;
x0 = x1, y0 = y1;
return [dx, dy];
});
}
tribin.triangle = function(side, rotation, center) {
side = (side == null ? s : +side),
rotation = (rotation == null ? 0 : +rotation);
var moveCenter = (center == null ? "" : "M" + center[0] + "," + center[1]);
return moveCenter + "m" + triangle(side, rotation).join("l") + "z";
}
tribin.triangleFromBin = function(d, side) {
side = (side == null ? s : +side);
return tribin.triangle(side, d.rotation, [d.x, d.y]);
}
tribin.x = function(_) {
return arguments.length ? (x = _, tribin) : x;
};
tribin.y = function(_) {
return arguments.length ? (y = _, tribin) : y;
};
tribin.side = function(_) {
return arguments.length ? (s = +_, inR = s * sqrt3 / 6, outR = s * sqrt3 / 3, h = inR + outR, tribin) : s;
};
return tribin.side(1);
};
exports.tribin = tribin;
Object.defineProperty(exports, '__esModule', { value: true });
}));
<!DOCTYPE html>
<style>
.hexagon {
stroke: white;
stroke-width: 0.5px;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="d3-tribin.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var randomX = d3.randomNormal(width / 2, 80),
randomY = d3.randomNormal(height / 2, 80),
points = d3.range(2000).map(function() { return [randomX(), randomY()]; });
var triangleSide = 35,
tribin = d3.tribin().side(triangleSide),
bins = tribin(points),
binsLength = bins.map(function (bin) { return bin.length; }),
scaleColor = d3.scaleSequential(d3.interpolateLab("white", "steelblue")).domain([0, d3.max(binsLength)]);
var x = d3.scaleLinear()
.domain([0, width])
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, height])
.range([height, 0]);
g.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
g.append("g")
.attr("class", "hexagon")
.attr("clip-path", "url(#clip)")
.selectAll("path")
.data(bins)
.enter().append("path")
.attr("d", function(d) { return tribin.triangleFromBin(d); })
.attr("fill", function(d) { return scaleColor(d.length); });
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).tickSizeOuter(-width));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickSizeOuter(-height));
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment