This variation of my California population density map uses geo2svg to generate a static choropleth from the command line!
<!DOCTYPE html>
<img src="topo.svg" width="960" height="1100">
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="960" height="1100" viewBox="0 0 960 1100" fill="none">
<g transform="translate(540,40)" font-size="10" font-family="sans-serif" text-anchor="middle">
<rect height="8" x="0" width="6" fill="#fff7ec"></rect>
<rect height="8" x="6" width="13" fill="#fee8c8"></rect>
<rect height="8" x="19" width="23" fill="#fdd49e"></rect>
<rect height="8" x="42" width="42" fill="#fdbb84"></rect>
<rect height="8" x="84" width="49" fill="#fc8d59"></rect>
<rect height="8" x="133" width="56" fill="#ef6548"></rect>
<rect height="8" x="189" width="78" fill="#d7301f"></rect>
<rect height="8" x="267" width="110" fill="#b30000"></rect>
<rect height="8" x="377" width="23" fill="#7f0000"></rect>
<text x="0" y="-6" fill="#000" text-anchor="start" font-weight="bold">Population per square mile</text>
<g transform="translate(6,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">1</text>
</g>
<g transform="translate(19,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">10</text>
</g>
<g transform="translate(42,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">50</text>
</g>
<g transform="translate(84,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">200</text>
</g>
<g transform="translate(133,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">500</text>
</g>
<g transform="translate(189,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">1,000</text>
</g>
<g transform="translate(267,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">2,000</text>
</g>
<g transform="translate(377,0)">
<line stroke="#000" y2="13" x1="0.5" x2="0.5"></line>
<text fill="#000" y="16" x="0.5" dy="0.71em">4,000</text>
</g>
</g>
</svg>
{
"private": true,
"license": "gpl-3.0",
"author": {
"name": "Mike Bostock",
"url": "https://bost.ocks.org/mike"
},
"scripts": {
"prepublish": "bash prepublish"
},
"devDependencies": {
"d3-array": "^1.0.1",
"d3-geo-projection": "^1.2.1",
"d3-scale-chromatic": "^1.1.0",
"ndjson-cli": "^0.3.0",
"shapefile": "^0.5.8",
"topojson": "^2.0.0",
"topojson-client": "^2.1.0",
"topojson-simplify": "^2.0.0"
}
}
#!/bin/bash
# EPSG:3310 California Albers
PROJECTION='d3.geoAlbers().parallels([34, 40.5]).rotate([120, 0])'
# The state FIPS code.
STATE=06
# The ACS 5-Year Estimate vintage.
YEAR=2014
# The display size.
WIDTH=960
HEIGHT=1100
# Download the census block group boundaries.
# Extract the shapefile (.shp) and dBASE (.dbf).
if [ ! -f cb_${YEAR}_${STATE}_bg_500k.shp ]; then
curl -o cb_${YEAR}_${STATE}_bg_500k.zip \
"http://www2.census.gov/geo/tiger/GENZ${YEAR}/shp/cb_${YEAR}_${STATE}_bg_500k.zip"
unzip -o \
cb_${YEAR}_${STATE}_bg_500k.zip \
cb_${YEAR}_${STATE}_bg_500k.shp \
cb_${YEAR}_${STATE}_bg_500k.dbf
fi
# Download the list of counties.
if [ ! -f cb_${YEAR}_${STATE}_counties.json ]; then
curl -o cb_${YEAR}_${STATE}_counties.json \
"http://api.census.gov/data/${YEAR}/acs5?get=NAME&for=county:*&in=state:${STATE}&key=${CENSUS_KEY}"
fi
# Download the census block group population estimates for each county.
if [ ! -f cb_${YEAR}_${STATE}_bg_B01003.ndjson ]; then
for COUNTY in $(ndjson-cat cb_${YEAR}_${STATE}_counties.json \
| ndjson-split \
| tail -n +2 \
| ndjson-map 'd[2]' \
| cut -c 2-4); do
echo ${COUNTY}
if [ ! -f cb_${YEAR}_${STATE}_${COUNTY}_bg_B01003.json ]; then
curl -o cb_${YEAR}_${STATE}_${COUNTY}_bg_B01003.json \
"http://api.census.gov/data/${YEAR}/acs5?get=B01003_001E&for=block+group:*&in=state:${STATE}+county:${COUNTY}&key=${CENSUS_KEY}"
fi
ndjson-cat cb_${YEAR}_${STATE}_${COUNTY}_bg_B01003.json \
| ndjson-split \
| tail -n +2 \
>> cb_${YEAR}_${STATE}_bg_B01003.ndjson
done
fi
# 1. Convert to GeoJSON.
# 2. Project.
# 3. Join with the census data.
# 4. Compute the population density.
# 5. Simplify.
# 6. Compute the county borders.
geo2topo -n \
blockgroups=<(ndjson-join 'd.id' \
<(shp2json cb_${YEAR}_${STATE}_bg_500k.shp \
| geoproject "${PROJECTION}.fitExtent([[10, 10], [${WIDTH} - 10, ${HEIGHT} - 10]], d)" \
| ndjson-split 'd.features' \
| ndjson-map 'd.id = d.properties.GEOID.slice(2), d') \
<(cat cb_${YEAR}_${STATE}_bg_B01003.ndjson \
| ndjson-map '{id: d[2] + d[3] + d[4], B01003: +d[0]}') \
| ndjson-map -r d3=d3-array 'd[0].properties = {density: d3.bisect([1, 10, 50, 200, 500, 1000, 2000, 4000], (d[1].B01003 / d[0].properties.ALAND || 0) * 2589975.2356)}, d[0]') \
| topomerge -k 'd.id.slice(0, 3)' counties=blockgroups \
| topomerge --mesh -f 'a !== b' counties=counties \
| topomerge -k 'd.properties.density' blockgroups=blockgroups \
| toposimplify -p 1 -f \
> topo.json
# Convert to SVG (while dropping the last line).
cat \
<(topo2geo -n \
< topo.json blockgroups=- \
| ndjson-map -r d3=d3-scale-chromatic '(d.properties.title = d.id, d.properties.fill = d3.schemeOrRd[9][d.id], d)') \
<(topo2geo -n \
< topo.json counties=- \
| ndjson-map '(d.properties.stroke = "black", d.properties.strokeOpacity = 0.3, d)') \
| geo2svg --stroke=none -n -p 1 -w ${WIDTH} -h ${HEIGHT} \
| sed '$d' \
> topo.svg
# Insert the legend.
tail -n +4 \
< legend.svg \
>> topo.svg
rm topo.json