block by emeeks 43acd86abaeceeb0cd59

Aggregate Value Graduated Hexbin

Full Screen

A graduated size and color hexbin that displays the total value of diamonds of a particular carat and depth from a dataset of 50k+ such diamonds. The style follows that of Scatterplot Matrix Techniques for Large N. Graduated hexbins that aggregate value do a better job of showing spatial distribution of values, as contrasted with graduated symbols that occlude each other as well as the region their purport to represent.

Here’s a version that shows the raw number of items in each hex using the same color and symbology.

index.html


<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="utf-8" />
<title>Aggregate Value Graduated Size and Color Hexbin</title>
<style>

svg {
  background: #3b7e77;
}
.node {
  stroke: #fff;
  stroke-width: 1.5px;
}

.link {
  fill: none;
  stroke: #000;
  stroke-width: 1.5px;
  opacity: 0.4;
  marker-end: url(#end-arrow);
}

.domain {
  fill: none;
  stroke: #5ba8b8;
}

.tick > line {
  stroke: #5ba8b8;
}

.tick > text {
  fill: #5ba8b8;
}

</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js" charset="utf-8" type="text/javascript"></script>
<script src="d3.hexbin.js" charset="utf-8" type="text/javascript"></script>
<script>

var xAtt = "carat";
var yAtt = "depth";
var hexsize = 5;

bgColors = d3.scale.threshold().domain([1,1000,100000,1000000,5000000]).range(["none","none","#0481ad","#39957c","#c18779"]);
fgColors = d3.scale.threshold().domain([1,1000,100000,1000000,5000000]).range(["none","#1d3b3d","#035c94","#386b5c","#363b37"]);

priceSizeScale = d3.scale.linear().domain([1,1000,1001,100000,100001,1000000,1000001,5000000]).range([0,hexsize,0,hexsize,0,hexsize,0,hexsize]);

//These attributes could be derived from the data
attributes = ["carat","depth","table","price","x","y","z"];

d3.csv("https://gist.githubusercontent.com/emeeks/613cd391ba7e9a3a4042/raw/5c3711f9e3f55bea40ce21f765dc373a28cf72de/diamonds.csv", hexbin);

d3.select("body").append("svg").attr("height", 850).attr("width", 800)

function hexbin(data) {

  //d3.csv pulls in data as strings so they need to be formatted as numbers
  data.forEach(function (d) {
    attributes.forEach(function (att) {
      d[att] = parseFloat(d[att])
    })
  })

  xExtent = d3.extent(data, function (d) {return d[xAtt]});
  xScale = d3.scale.linear();
  xScale.domain(xExtent).range([20,995]);

  yExtent = d3.extent(data, function (d) {return d[yAtt]});
  console.log(yExtent)
  yScale = d3.scale.linear();
  yScale.domain(yExtent).range([995,20]);

  var hexbin = d3.hexbin()
    .size([1000,1000])
    .x(function (d) {return xScale(d[xAtt])})
    .y(function (d) {return yScale(d[yAtt])})
    .radius(hexsize);

  //bind the matrix array to a grid of g elements
  d3.select("svg")
  .append("g")
  .attr("class", "hexbin")
  .attr("transform", function (d) {return "translate(30,-200)" });

  d3.select("g")
    .selectAll("path.bg")
    .data(hexbin(data))
    .enter()
    .append("path")
    .style("fill", function (d) {return bgColors(d3.sum(d, function (d) {return d.price}))})
    .style("stroke", function (d) {return bgColors(d3.sum(d, function (d) {return d.price}))})
    .attr("d", function(d) { return hexbin.hexagon(hexsize); })
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

  d3.select("g")
    .selectAll("path.fg")
    .data(hexbin(data))
    .enter()
    .append("path")
    .style("fill", function (d) {return fgColors(d3.sum(d, function (d) {return d.price}))})
    .style("stroke", function (d) {return fgColors(d3.sum(d, function (d) {return d.price}))})
    .attr("d", lastNumber)
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(10)
.tickSize(0)
.tickSubdivide(true);

xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(10)
.tickSize(0)
.tickSubdivide(true);

  d3.select("g.hexbin").append("g").call(yAxis)

  d3.select("g.hexbin").append("g").attr("transform", "translate(0,1000)").call(xAxis)

function lastNumber(d) {

  var sl = d3.sum(d, function (d) {return d.price});

  return hexbin.hexagon(priceSizeScale(sl));
}

}



</script>
</body>
</html>

d3.hexbin.js

(function() {

d3.hexbin = function() {
  var width = 1,
      height = 1,
      r,
      x = d3_hexbinX,
      y = d3_hexbinY,
      dx,
      dy;

  function hexbin(points) {
    var binsById = {};

    points.forEach(function(point, i) {
      var py = y.call(hexbin, point, i) / dy, pj = Math.round(py),
          px = x.call(hexbin, point, i) / dx - (pj & 1 ? .5 : 0), pi = Math.round(px),
          py1 = py - pj;

      if (Math.abs(py1) * 3 > 1) {
        var px1 = px - pi,
            pi2 = pi + (px < pi ? -1 : 1) / 2,
            pj2 = pj + (py < pj ? -1 : 1),
            px2 = px - pi2,
            py2 = py - pj2;
        if (px1 * px1 + py1 * py1 > px2 * px2 + py2 * py2) pi = pi2 + (pj & 1 ? 1 : -1) / 2, pj = pj2;
      }

      var id = pi + "-" + pj, bin = binsById[id];
      if (bin) bin.push(point); else {
        bin = binsById[id] = [point];
        bin.i = pi;
        bin.j = pj;
        bin.x = (pi + (pj & 1 ? 1 / 2 : 0)) * dx;
        bin.y = pj * dy;
      }
    });

    return d3.values(binsById);
  }

  function hexagon(radius) {
    var x0 = 0, y0 = 0;
    return d3_hexbinAngles.map(function(angle) {
      var x1 = Math.sin(angle) * radius,
          y1 = -Math.cos(angle) * radius,
          dx = x1 - x0,
          dy = y1 - y0;
      x0 = x1, y0 = y1;
      return [dx, dy];
    });
  }

  hexbin.x = function(_) {
    if (!arguments.length) return x;
    x = _;
    return hexbin;
  };

  hexbin.y = function(_) {
    if (!arguments.length) return y;
    y = _;
    return hexbin;
  };

  hexbin.hexagon = function(radius) {
    if (arguments.length < 1) radius = r;
    return "m" + hexagon(radius).join("l") + "z";
  };

  hexbin.centers = function() {
    var centers = [];
    for (var y = 0, odd = false, j = 0; y < height + r; y += dy, odd = !odd, ++j) {
      for (var x = odd ? dx / 2 : 0, i = 0; x < width + dx / 2; x += dx, ++i) {
        var center = [x, y];
        center.i = i;
        center.j = j;
        centers.push(center);
      }
    }
    return centers;
  };

  hexbin.mesh = function() {
    var fragment = hexagon(r).slice(0, 4).join("l");
    return hexbin.centers().map(function(p) { return "M" + p + "m" + fragment; }).join("");
  };

  hexbin.size = function(_) {
    if (!arguments.length) return [width, height];
    width = +_[0], height = +_[1];
    return hexbin;
  };

  hexbin.radius = function(_) {
    if (!arguments.length) return r;
    r = +_;
    dx = r * 2 * Math.sin(Math.PI / 3);
    dy = r * 1.5;
    return hexbin;
  };

  return hexbin.radius(1);
};

var d3_hexbinAngles = d3.range(0, 2 * Math.PI, Math.PI / 3),
    d3_hexbinX = function(d) { return d[0]; },
    d3_hexbinY = function(d) { return d[1]; };

})();