Skip to content

Instantly share code, notes, and snippets.

@feyderm
Last active April 26, 2019 06:25
Show Gist options
  • Save feyderm/9b3e95af24f8b8078aae255bba5796fd to your computer and use it in GitHub Desktop.
Save feyderm/9b3e95af24f8b8078aae255bba5796fd to your computer and use it in GitHub Desktop.
Geographic, interactive hexbin
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src = "https://feyderm.github.io/d3/d3.js"></script>
<script src = "https://feyderm.github.io/d3/hexbin/hexbin.js"></script>
<script src = "https://feyderm.github.io/d3/d3-legend-master/d3-legend.js"></script>
<script src = "https://feyderm.github.io/js/viridis_colors.js"></script>
</head>
<body>
<div id="block"></div>
<script>
var h = 700;
var w = 700;
var svg = d3.select("#block")
.append("svg")
.attr("height", h)
.attr("width", w);
var basemap = svg.append("g")
.attr("id", "basemap");
var projection = d3.geo.mercator()
.center([-79.986174, 40.442920])
.scale([150000])
.translate([310, 290]);
// basemap
d3.json("https://feyderm.github.io/data/pgh_neighborhoods.geojson", function(json) {
var path = d3.geo.path()
.projection(projection);
basemap.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.attr("stroke", "grey")
.attr("fill", "#b3b3b3");
});
// define hexbins
var hexbin = d3.hexbin()
.size([w, h])
.radius(4);
var hex = svg.append("g")
.attr("id", "hexbins");
// first of two scales for linear hexagon fill - ref[1]
var fill_scale1 = d3.scale.linear()
.domain(d3.range(0, 1, 1 / (viridis_colors.length - 1)))
.range(viridis_colors);
// crash data
d3.csv("https://feyderm.github.io/data/pgh_bike_crashes_coords_2004_2014.csv", function(data) {
// convert lat/lng to numeric
data.forEach(function(d) {
d.lat = +d.lat;
d.lng = +d.lng;
});
points = [];
// x,y maps to lng,lat - ref[2]
data.forEach(function(d) {
d.lat = +d.lat;
d.lng = +d.lng;
var x = projection([d.lng, d.lat])[0];
var y = projection([d.lng, d.lat])[1];
points.push([x, y]);
});
// bin coords
var bins = hexbin(points);
var bins_n = []; // points per hexbin
bins.forEach(function(d) {
bins_n.push(d.length);
});
// second of two scales for linear hexagon fill - ref[1]
var extent = d3.extent(bins_n);
var fill_scale2 = d3.scale.linear()
.domain([extent[0], extent[1]])
.range([0,1]);
hex.selectAll(".hexagon")
.data(hexbin(points))
.enter()
.append("path")
.attr("class", function(d) {
return "hexagon bin_" + d.length;
})
.attr("d", hexbin.hexagon())
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.style("fill", function(d) {
return fill_scale1(fill_scale2(d.length));
});
// legend
svg.append("g")
.attr("class", "legendQuant")
.attr("transform", "translate(20,20)");
var legend_hex = "m0,-12l10.392304845413264,5.999999999999998l0,12l-10.392304845413257,6.000000000000003l-10.392304845413268,-5.999999999999995l-7.105427357601002e-15,-11.999999999999996l10.392304845413253,-6.000000000000008z";
var legend = d3.legend.color()
.scale(fill_scale1)
.cells(7)
.shape("path", legend_hex)
.orient("horizontal")
.shapePadding(8)
.labelOffset(10)
.title("Number of Bike Accidents")
.on("cellclick", function(d) {
d3.selectAll(".hexagon")
.style("opacity", "1");
var n_bin = Math.ceil(fill_scale2.invert(d))
d3.selectAll(".hexagon:not(.bin_" + n_bin)
.style("opacity", "0");
});
svg.select(".legendQuant")
.call(legend);
// re-map legend text from fill_scale1 (0-1) to binned data (min - max)
d3.selectAll(".label")
.text(function () {
label = d3.select(this).text();
return Math.round(fill_scale2.invert(label));
});
// tweak position of legend title
d3.select(".legendTitle")
.attr("transform", "translate(1, 6)");
// new cursor pointer for legend hexs and reset button
d3.selectAll(".swatch, #buttonBackground")
.on("mouseover", function() {
d3.select(this)
.style("cursor", "pointer");
});
// cursor unresponsive to legend text
d3.selectAll(".label")
.on("mouseover", function() {
d3.select(this)
.style("cursor", "default");
});
});
// reset button - credit ref[3]
var b_buttonColor = "#3399ff";
var b_width= 110 / 2,
b_height=45 / 2,
b_fontSize = 1.38 * b_height / 3,
b_x0 = 20,
b_y0 = 95,
b_x0Text = b_x0 + b_width / 2,
b_y0Text = b_y0 + 0.66 * b_height,
b_text = "Reset";
var reset = svg.append("g")
.attr("id", "reset");
reset.append("rect")
.attr("id","buttonBackground")
.attr("width", b_width + "px")
.attr("height", b_height + "px")
.style("fill", b_buttonColor)
.attr("x", b_x0)
.attr("y", b_y0)
.attr("ry", b_height/10)
.attr("r", 30)
.on("click", function() {
d3.selectAll(".hexagon")
.style("opacity", "1");
});
reset.append("text")
.attr("id","buttonText")
.attr("x", b_x0Text)
.attr("y", b_y0Text)
.style("text-anchor", "middle")
.style("fill", "#ffffff")
.style("stroke", "none")
.style("font-family", "Arial, sans-serif")
.style("font-size", b_fontSize + "px")
.style("pointer-events", "none")
.text(b_text);
/*REFERENCES*/
/*ref[1]: http://stackoverflow.com/questions/17671252/d3-create-a-continous-color-scale-with-many-strings-inputs-for-the-range-and-dy*/
/*ref[2]: http://gis.stackexchange.com/questions/99769/why-some-coordinate-systems-define-x-axis-as-northings-and-some-as-easting/99781#99781*/
/*ref[3]: http://bl.ocks.org/pbogden/7487564 */
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment