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/bf5c4ea014d0b91f1f04 to your computer and use it in GitHub Desktop.
Save nitaku/bf5c4ea014d0b91f1f04 to your computer and use it in GitHub Desktop.
Classic hexagonal binning II

An example of hexagonal binning using color 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 color (the darker, the greater the amount).

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 colors).

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

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)
color = d3.scale.linear()
.domain([0, d3.max(bins, (b) -> b.length)])
.range(['white', '#333'])
.interpolate(d3.interpolateHcl)
hexagons = d3.select('#bins').selectAll('.hexagon')
.data(bins)
hexagons.enter().append('path')
.attr('class', 'hexagon')
.attr('d', (d) -> hexbin.hexagon(radius))
.attr('transform', (d) -> "translate(#{d.x},#{d.y})")
.attr('fill', (d) -> color(d.length))
svg {
background-color: white;
}
.separator {
stroke: #DEDEDE;
fill: none;
shape-rendering: crispEdges;
}
.hexagon {
shape-rendering: crispEdges;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Classic hexagonal binning II" />
<title>Classic hexagonal binning II</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, color, dots, height, hexagons, hexbin, points, radius, 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);
color = d3.scale.linear().domain([
0, d3.max(bins, function(b) {
return b.length;
})
]).range(['white', '#333']).interpolate(d3.interpolateHcl);
hexagons = d3.select('#bins').selectAll('.hexagon').data(bins);
hexagons.enter().append('path').attr('class', 'hexagon').attr('d', function(d) {
return hexbin.hexagon(radius);
}).attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).attr('fill', function(d) {
return color(d.length);
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment