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).
(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);
<!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="//d3js.org/d3.v3.min.js"></script>
<script src="//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>
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;
}