Last active
April 26, 2019 06:25
-
-
Save feyderm/9b3e95af24f8b8078aae255bba5796fd to your computer and use it in GitHub Desktop.
Geographic, interactive hexbin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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