block by nitaku bf5c4ea014d0b91f1f04

Classic hexagonal binning II

Full Screen

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

index.js

(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);

index.html

<!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="//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>

index.coffee

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

index.css

svg {
  background-color: white;
}
.separator {
  stroke: #DEDEDE;
  fill: none;
  shape-rendering: crispEdges;
}
.hexagon {
  shape-rendering: crispEdges;
}