A visualization combining maps with a Voronoi diagram. Tornado sightings in 2013 from NOAA.
Last active
August 20, 2018 21:16
-
-
Save sathomas/c294e8a62c98fc41ef1b to your computer and use it in GitHub Desktop.
GeoJSON with Voronoi
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> | |
<html> | |
<head> | |
<meta charset='utf-8'> | |
<title>GeoJSON with Voronoi</title> | |
<link href='http://fonts.googleapis.com/css?family=Varela' rel='stylesheet' | |
type='text/css'> | |
<style> | |
body { font: 18px Varela,sans-serif; } | |
path { cursor: pointer; } | |
</style> | |
</head> | |
<body> | |
<div style="position:absolute;top:455px"> | |
<input type="checkbox" id="voronoi"> | |
<label for="voronoi" style="position:relative;top:2px;">Voronoi</label> | |
</div> | |
<!-- | |
Because of cross-origin restrictions, we have to use | |
a hack to load our map data and tornado dataset. | |
--> | |
<script src='http://jsDataV.is/data/us-states.js'></script> | |
<script src='http://jsDataV.is/data/tornadoes.js'></script> | |
<script src='http://d3js.org/d3.v3.min.js'></script> | |
<script> | |
// Define the dimensions of the visualization. | |
var margin = {top: 20, right: 20, bottom: 20, left: 20}, | |
width = 636 - margin.left - margin.right, | |
height = 446 - margin.top - margin.bottom; | |
// Create the SVG container for the visualization and | |
// define its dimensions. | |
var svg = d3.select("body").append("svg") | |
.attr("width", width + margin.left + margin.right) | |
.attr("height", height + margin.top + margin.bottom); | |
// Within the main SVG container, add a group | |
// element (`<g>`) that can be transformed via | |
// a translation to account for the margins. | |
// Within that group create another group element | |
// that can be used to zoom and pan the map. | |
var g = svg.append("g") | |
.attr("transform", "translate(" + margin.left + | |
"," + margin.top + ")") | |
.append("g"); | |
// Define a variable that tracks which state is | |
// currently zoomed (if any) and a variable that | |
// indicates if the Voronoi diagram is visible. | |
var active = d3.select(null), | |
voronoi = false; | |
// Set up an event handler to respond to the | |
// Voronoi checkbox. | |
d3.select("input[type=checkbox]").on("change", function() {toggle();}); | |
// Define the properties of the map projection. | |
var projection = d3.geo.albers() | |
.scale(900) | |
.translate([width / 2, height / 2]); | |
// Define a function that returns the SVG | |
// path based on the projection. This | |
// function accepts, as input, a selection | |
// with an associated array of longitude | |
// and latitude values. | |
var path = d3.geo.path() | |
.projection(projection); | |
// Draw the map within the SVG container. | |
// Each state is a separate SVG path. | |
g.selectAll("path") | |
.data(map.features) | |
.enter().append("path") | |
.attr("id", function(d) { | |
return d.properties.abbreviation; | |
}) | |
.attr("d", path) | |
.attr("fill", "#cccccc") | |
.attr("stroke", "#ffffff") | |
.on("click", clicked); | |
// Create a function that will parse | |
// the text date format in the CSV file | |
// and return a proper JavaScript date. | |
// Dates in the file look like, e.g., | |
// | |
// 30-JAN-13 04:33:00 | |
// | |
// Note that we're not considering time | |
// zone information because we want to | |
// use local time for any visualization. | |
var formatDate = d3.time.format( | |
"%d-%b-%y %H:%M:%S"); | |
// Only consider data points that have | |
// latitude and longitude values. While | |
// we're checking this condition, coerce | |
// the CSV strings into data types that | |
// we can work with directly. | |
var data = dataset.filter(function(d, i) { | |
if (d.latitude && d.longitude) { | |
// Convert the string date into | |
// a real one. | |
d.date = formatDate.parse(d.date); | |
// Convert the strings for latitude | |
// and longitude into numbers. | |
d.latitude = +d.latitude; | |
d.longitude = +d.longitude; | |
// Convert the F scale string to | |
// a number. | |
d.f_scale = +d.f_scale[2]; | |
// Calculate the position of the | |
// point within the projection. | |
d.position = projection([ | |
d.longitude, d.latitude | |
]); | |
return true; | |
} | |
}); | |
// Compute the polygons for the Voronoi layout. | |
// Before we can use D3's Voronoi functions, we | |
// have to filter out any duplicate positions. | |
var positions = data.map(function(d) { return d.position;}) | |
.reduce(function(positions, position) { | |
if (!positions.some(function(p) { | |
return position[0] === p[0] && position[1] === p[1]; | |
})) { | |
positions.push(position); | |
} | |
return positions; | |
}, []); | |
var polygons = d3.geom.voronoi(positions); | |
// Now we can add the Voronoi polygons to the | |
// graph. Initially they're invisible because | |
// the stroke opacity is set to zero. | |
g.selectAll(".cell") | |
.data(polygons) | |
.enter().append("path") | |
.attr("class", "cell") | |
.attr("d", function(d) { return "M" + d.join("L") + "Z"; }) | |
.attr("stroke", "#007979") | |
.attr("stroke-opacity", 0) | |
.attr("fill", "none"); | |
// Draw circles on the map for each | |
// data point. | |
g.selectAll("circle") | |
.data(data) | |
.enter().append("circle") | |
.attr("cx", function(d) { return d.position[0]; }) | |
.attr("cy", function(d) { return d.position[1]; }) | |
.attr("r", function(d) { return 4 + 2*d.f_scale; }) | |
.attr("stroke", "#dddddd") | |
.attr("fill", "#ca0000") | |
.attr("fill-opacity", "0.8"); | |
// Event handlers. | |
// Click on a state. | |
function clicked(d) { | |
// If clicked on state is already active, | |
// reset the map to its initial condition. | |
if (active.node() === this) return reset(); | |
// Otherwise, remove the highlighting from | |
// the currently active state. | |
active.attr("fill", "#cccccc"); | |
// And add highlighting to the newly | |
// active state. | |
active = d3.select(this) | |
.attr("fill", "#F77B15"); | |
// Calculate the bounds for the map that | |
// will contain the newly active state. | |
var bounds = path.bounds(d), | |
dx = bounds[1][0] - bounds[0][0], | |
dy = bounds[1][1] - bounds[0][1], | |
x = (bounds[0][0] + bounds[1][0]) / 2, | |
y = (bounds[0][1] + bounds[1][1]) / 2, | |
scale = .9 / Math.max(dx / width, dy / height), | |
translate = [width / 2 - scale * x, height / 2 - scale * y]; | |
// Transition to the newly active state | |
// by translation and scaling. | |
g.transition() | |
.duration(750) | |
.style("stroke-width", 1.5 / scale + "px") | |
.attr("transform", "translate(" + translate + ")scale(" + scale + ")"); | |
// To keep the circles from changing | |
// size, also transition their radii. | |
g.selectAll("circle") | |
.transition() | |
.duration(750) | |
.attr("r", function(d) { return (4 + 2*d.f_scale)/scale; }); | |
}; | |
// Reset to initial condition. | |
function reset() { | |
// Remove highlighting from active state | |
// and note that no state is now active. | |
active.attr("fill", "#cccccc"); | |
active = d3.select(null); | |
// Remove the translation and scale | |
// transform with a transition. | |
g.transition() | |
.duration(750) | |
.style("stroke-width", "1px") | |
.attr("transform", ""); | |
// Also keep the circles the same | |
// size by transitioning their | |
// radii at the same time. | |
g.selectAll("circle") | |
.transition() | |
.duration(750) | |
.attr("r", function(d) { return (4 + 2*d.f_scale); }); | |
}; | |
// Toggle the visibility of the Voronoi | |
// overlay. | |
function toggle() { | |
g.selectAll(".cell") | |
.transition() | |
.duration(750) | |
.attr("stroke-opacity", voronoi ? 0 : 1); | |
voronoi = !voronoi; | |
}; | |
</script> | |
</body> | |
</html> |
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
http://jsdatav.is/img/thumbnails/tornadoes.png |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment