block by armollica d19483f314189df73fe0235bc633ae59

Joymap

Full Screen

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

index.html

<!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>

Makefile


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

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')