Population of Wisconsin.
Not totally satisfied with the result so far. Need to deal with coastlines and borders differently. Also there’s a weirdly shaped lump on Stevens Point.
Inspired by Population Lines
<!DOCTYPE html>
<html>
<head>
<style>
svg {
background-color: #ddd;
}
.line {
fill: none;
stroke: #666;
}
.area {
fill: #ddd;
}
</style>
</head>
<body>
<svg width="960" height="800"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var yOffset = 0.33;
function row(d) {
return {
x: +d.x,
y: +d.y,
population: +d.population
};
}
d3.queue()
.defer(d3.csv, 'population-grid.csv', row)
.defer(d3.json, 'wisconsin.json')
.await(ready);
function ready(error, data, wisconsin) {
if (error) throw error;
var svg = d3.select('svg'),
width = +svg.attr('width'),
height = +svg.attr('height'),
extent = [[0, 20],[width, height - 20]];
var projection = d3.geoIdentity()
.reflectY(true)
.fitExtent(extent, wisconsin);
var path = d3.geoPath()
.projection(projection);
svg.append('defs').append('clipPath').attr('id', 'wisconsin-border')
.append('path').datum(wisconsin)
.attr('d', path);
var tx = projection.translate()[0],
ty = projection.translate()[1],
k = projection.scale(),
x = function(d) { return (d * k) + tx; },
y = function(d) { return (d * -k) + ty; };
var dy = d3.scaleLinear()
.domain([0, d3.max(data, function(d) { return d.population; })])
.range([0, -height * yOffset]);
var curve = d3.curveCardinal.tension(0);
var line = d3.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return dy(d.population); })
.curve(curve);
var area = d3.area()
.x(function(d) { return x(d.x); })
.y0(dy(0))
.y1(function(d) { return dy(d.population); })
.curve(curve);
var lineData = d3.nest()
.key(function(d) { return d.y; })
.entries(data)
.reverse();
var lump = svg.append('g').attr('class', 'lumps')
.attr('clip-path', 'url(#wisconsin-border)')
.selectAll('.lump').data(lineData)
.enter().append('g')
.attr('class', 'lump')
.attr('transform', function(d) { return 'translate(0,' + y(+d.key) + ')'})
.datum(function(d) { return d.values; });
lump.append('path')
.attr('class', 'area')
.attr('d', area);
lump.append('path')
.attr('class', 'line')
.attr('d', line);
}
</script>
</body>
</html>
all: population-grid.csv wisconsin.json
csv/population.csv:
mkdir -p $(dir $@)
curl 'http://api.census.gov/data/2015/acs5?get=B01003_001E&for=tract:*&in=state:55' \
| ndjson-cat \
| ndjson-split 'd.slice(1)' \
| ndjson-map '{id: d[2] + d[3], population: +d[0]}' \
| json2csv -n \
> $@
zip/tl_2015_55_tract.zip:
mkdir -p $(dir $@)
curl -o $@.download 'ftp://ftp2.census.gov/geo/tiger/TIGER2015/TRACT/tl_2015_55_tract.zip'
mv $@.download $@
shp/tl_2015_55_tract.shp: zip/tl_2015_55_tract.zip
mkdir -p $(dir $@)
unzip -d $(dir $@) $<
touch $@
gz/statesp010g.shp_nt00938.tar.gz:
mkdir -p $(dir $@)
curl -o $@.download 'https://prd-tnm.s3.amazonaws.com/StagedProducts/Small-scale/data/Boundaries/statesp010g.shp_nt00938.tar.gz'
mv $@.download $@
shp/statesp010g.shp: gz/statesp010g.shp_nt00938.tar.gz
mkdir -p $(dir $@)
tar -xzm -C $(dir $@) -f $<
wisconsin.json: shp/statesp010g.shp
shp2json shp/statesp010g.shp \
| ndjson-split 'd.features' \
| ndjson-filter 'd.properties.STATE_FIPS === "55" && d.properties.TYPE === "Land"' \
| ndjson-reduce \
| ndjson-map '{type: "FeatureCollection", features: d}' \
| mapshaper \
-i - \
-proj '+proj=tmerc +lat_0=0 +lon_0=-90 +k=0.9996 +x_0=520000 +y_0=-4480000 +ellps=GRS80 +units=m +no_defs' \
-o - \
> $@
population-grid.csv: csv/population.csv shp/tl_2015_55_tract.shp shp/statesp010g.shp
Rscript create-population-grid.R
#!/usr/bin/env Rscript
library(tidyverse)
library(stringr)
library(sf)
wisconsin <- read_sf('shp/statesp010g.shp') %>%
filter(STATE_FIPS == '55',
TYPE == 'Land')
tracts <- read_sf('shp/tl_2015_55_tract.shp') %>%
mutate(id = str_c(COUNTYFP, TRACTCE, sep = '')) %>%
filter(TRACTCE != "990000") %>%
st_intersection(wisconsin)
population_tracts <- read_csv('csv/population.csv') %>%
right_join(tracts, by = 'id') %>%
st_as_sf() %>%
select(id, population, geometry) %>%
st_transform(3071)
grid <- st_make_grid(population_tracts, n = c(400, 200))
population_grid<- =st_interpolate_aw(population_tracts['population'], grid, extensive = TRUE)
population_grid %>%
mutate(geometry = st_centroid(geometry),
x = geometry %>% map_dbl(~.[1]),
y = geometry %>% map_dbl(~.[2])) %>%
as_data_frame() %>%
select(x, y, population) %>%
write_csv('population-grid.csv')