Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nitaku/d3fd26207d7468b6c514 to your computer and use it in GitHub Desktop.
Save nitaku/d3fd26207d7468b6c514 to your computer and use it in GitHub Desktop.
Classic hexagonal binning

An example of hexagonal binning using area encoding. Reload to change the random distributions.

The dot plot on the left is made (hopefully) more readable by grouping points into hexagonal cells (bins), then representing the amount of points in each cell by controlling its area.

Data is normalized according to the maximum number of points in a single bin (corresponding to the largest hexagonon the map), so two different instances of this visualization cannot be compared between each other (they may use two different scales for areas).

Other types of encoding can be used to represent the size of bins (e.g. color).

width = 480
height = 500
# DATA
points = d3.range(2000).map( () -> {x: d3.random.normal(width/3, 90)(), y: d3.random.normal(height/3, 90)()} )
.concat d3.range(1000).map( () -> {x: d3.random.normal(2*width/3, 70)(), y: d3.random.normal(2*height/3, 70)()} )
svg = d3.select('svg')
upper_left = svg.append('g')
.attr('id', 'dots')
.attr('clip-path', 'url(#square_window)')
upper_right = svg.append('g')
.attr('id', 'bins')
.attr('clip-path', 'url(#square_window)')
.attr('transform', "translate(#{width},0)")
svg.append('line')
.attr
class: 'separator'
x1: width
x2: width
y1: 0
y2: height
# dot density plot
dots = d3.select('#dots').selectAll('.dot')
.data(points)
dots.enter().append('circle')
.attr
class: 'dot'
r: 1
cx: (p) -> p.x
cy: (p) -> p.y
# hexagonal binning
radius = 16
apothem = Math.sqrt(3)/2 * radius
hexbin = d3.hexbin()
.size([width, height])
.radius(radius)
.x((d) -> d.x)
.y((d) -> d.y)
bins = hexbin(points)
radius_scale = d3.scale.sqrt()
.domain([0, d3.max(bins, (b) -> b.length)])
.range([0, radius])
hexagons = d3.select('#bins').selectAll('.hexagon')
.data(bins)
hexagons.enter().append('path')
.attr('class', 'hexagon')
.attr('d', (d) -> hexbin.hexagon(radius_scale(d.length)))
.attr('transform', (d) -> "translate(#{d.x},#{d.y})")
svg {
background-color: white;
}
.separator {
stroke: #DEDEDE;
fill: none;
shape-rendering: crispEdges;
}
.hexagon {
fill: #333;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Classic hexagonal binning" />
<title>Classic hexagonal binning</title>
<link rel="stylesheet" href="index.css">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.hexbin.v0.min.js?5c6e4f0"></script>
</head>
<body>
<svg height="500" width="960">
<defs>
<clipPath id="square_window">
<rect x="0" y="0" width="480.5" height="500.5" />
</clipPath>
</defs>
</svg>
<script src="index.js"></script>
</body>
</html>
(function() {
var apothem, bins, dots, height, hexagons, hexbin, points, radius, radius_scale, svg, upper_left, upper_right, width;
width = 480;
height = 500;
points = d3.range(2000).map(function() {
return {
x: d3.random.normal(width / 3, 90)(),
y: d3.random.normal(height / 3, 90)()
};
}).concat(d3.range(1000).map(function() {
return {
x: d3.random.normal(2 * width / 3, 70)(),
y: d3.random.normal(2 * height / 3, 70)()
};
}));
svg = d3.select('svg');
upper_left = svg.append('g').attr('id', 'dots').attr('clip-path', 'url(#square_window)');
upper_right = svg.append('g').attr('id', 'bins').attr('clip-path', 'url(#square_window)').attr('transform', "translate(" + width + ",0)");
svg.append('line').attr({
"class": 'separator',
x1: width,
x2: width,
y1: 0,
y2: height
});
dots = d3.select('#dots').selectAll('.dot').data(points);
dots.enter().append('circle').attr({
"class": 'dot',
r: 1,
cx: function(p) {
return p.x;
},
cy: function(p) {
return p.y;
}
});
radius = 16;
apothem = Math.sqrt(3) / 2 * radius;
hexbin = d3.hexbin().size([width, height]).radius(radius).x(function(d) {
return d.x;
}).y(function(d) {
return d.y;
});
bins = hexbin(points);
radius_scale = d3.scale.sqrt().domain([
0, d3.max(bins, function(b) {
return b.length;
})
]).range([0, radius]);
hexagons = d3.select('#bins').selectAll('.hexagon').data(bins);
hexagons.enter().append('path').attr('class', 'hexagon').attr('d', function(d) {
return hexbin.hexagon(radius_scale(d.length));
}).attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment