Published
Edited
Jun 26, 2021
1 fork
Importers
21 stars
Also listed in…
Political Issues
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// The main function
function make_map() {
let height = 0.625 * width;

// The map data is preprojected AlbersUSA
let path = d3.geoPath().projection(
d3
.geoIdentity()
.reflectY(true)
.fitSize([width, height], map_data.counties)
);

let div = d3
.create('div')
.style("width", `${width}px`)
.style("height", `${height}px`)
.style('overflow', 'hidden');

let svg = div
.append('svg')
.style("width", `${width}px`)
.style("height", `${height}px`)
.style('overflow', 'hidden');

let map = svg.append('g');
let counties = map
.append('g')
.selectAll("path.county")
.data(map_data.counties.features)
.join("path")
.attr('d', path)
.attr('class', 'county')
.attr('data-geoid', d => d.properties.GEOID)
// .attr('title', function(d) {
// return table_map.get(d.properties.GEOID);
// })
.style("fill", function(d) {
return color_map.get(d.properties.GEOID);
})
.style("stroke-width", '0.2px')
.style('stroke-opacity', 0.4)
.style("stroke", "#000")
.style("stroke-linejoin", "round");

counties
.on('mouseenter', function() {
d3.select(this)
.style('stroke-width', '1px')
.style('stroke-opacity', 1);
})
.on('mouseleave', function() {
d3.select(this)
.style('stroke-width', '0.2px')
.style('stroke-opacity', 0.4);
});
counties.nodes().forEach(c => {
let geoid = c.getAttribute('data-geoid');
let instance = tippy(c, {
allowHTML: true,
maxWidth: 420,
theme: 'light',
content: table_map.get(geoid),
onHide: function() {
d3.select(c)
.style('stroke-width', '0.2px')
.style('stroke-opacity', 0.4);
}
});
tippy_instances.set(c.getAttribute('data-geoid'), instance);
});

map
.append("g")
.selectAll("path.state")
.data(map_data.states)
.join("path")
.attr('class', 'state')
.attr('d', path)
.attr("fill", 'none')
.attr("stroke-width", 0.5)
.attr("stroke", "#fff")
.attr("stroke-linejoin", "round");
map
.append("g")
.selectAll("path.nation")
.data(map_data.nation)
.join("path")
.attr('class', 'nation')
.attr('d', path)
.attr("fill", 'none')
.attr("stroke-width", 0.5)
.attr("stroke", "#000")
.attr("stroke-linejoin", "round");

svg.call(
d3
.zoom()
.extent([[0, 0], [width, height]])
.translateExtent([[0, 0], [width, height]])
.scaleExtent([1, 4])
.duration(500)
.on('zoom', function(evt) {
map.attr("transform", evt.transform);
})
);

let div_node = div.node();
div_node.path = path;
return div_node;
}
Insert cell
tippy_instances = new Map()
Insert cell
function clone(selection, i) {
// Taken from this stackoverflow response:
// https://stackoverflow.com/questions/18517376/d3-append-duplicates-of-a-selection/18536991#18536991
let attr = selection.node().attributes;
let length = attr.length;
let node_name = selection.property("nodeName");
let parent = d3.select(selection.node().parentNode);
let cloned = parent.append(node_name).attr("id", selection.attr("id") + i);
for (let j = 0; j < length; j++) {
if (attr[j].nodeName == "id") continue;
cloned.attr(attr[j].name, attr[j].value);
}
return cloned;
}
Insert cell
Insert cell
// The population data comes from the most recent five year
// American Community Survey estimates, which is 2018:
// https://www.census.gov/data/developers/data-sets/acs-5year.html
// https://api.census.gov/data/2018/acs/acs5/groups/B02001.html

raw_data = (await (await fetch(
'https://api.census.gov/data/2018/acs/acs5?get=NAME,B02001_001E,B02001_002E,B02001_003E&for=county:*&in=state:*'
)).json()).slice(1)
Insert cell
map_data.counties.features[0].properties
Insert cell
county_name_to_fips_map = {
let cn2f_map = new Map();
map_data.counties.features.forEach(o =>
cn2f_map.set(
`${o.properties.NAME} County, ${fipsToStateMap.get(
o.properties.STATEFP
)}`,
o.properties.STATEFP + o.properties.COUNTYFP
)
);
return cn2f_map;
}
Insert cell
county_names = map_data.counties.features
.map(
c =>
`${c.properties.NAME} County, ${fipsToStateMap.get(c.properties.STATEFP)}`
)
.sort(county_compare)
Insert cell
fipsToStateMap = {
let fipsToStateMap = new Map();
map_data.states.forEach(s =>
fipsToStateMap.set(s.properties.STATEFP, s.properties.state_name)
);
return fipsToStateMap;
}
Insert cell
county_compare = function(c1, c2) {
// let county_name1, state_name1, county_name2, state_name2;
let [county_name1, state_name1] = c1.split(", ");
let [county_name2, state_name2] = c2.split(", ");
if (state_name1 < state_name2) {
return -1;
}
if (state_name1 > state_name2) {
return 1;
} else if (county_name1 < county_name2) {
return -1;
} else if (county_name1 > county_name2) {
return 1;
} else {
return 0;
}
}
Insert cell
// This map associates an HTML table with each county FIPS code
// to be shown as a tooltip.

table_map = {
let table_map = new Map();
raw_data.forEach(function([
place,
total,
white,
black,
state_fips,
county_fips
]) {
let div = d3.create('div'); // .style('background-color', 'white');
div.append('h3').text(`${place}`);
//.style('color', 'white');
let table = div.append('table');
let head = table.append('thead').append('tr');
head.append('th').text('Race');
head.append('th').text('Population');
head.append('th').text('Percentage');
let combined_row = table.append('tr');
combined_row.append('td').text('All');
combined_row.append('td').text(`${total}`);
let white_row = table.append('tr');
white_row.append('td').text('White');
white_row.append('td').text(`${white}`);
white_row.append('td').text(`${d3.format('0.2f')((100 * white) / total)}%`);
let black_row = table.append('tr');
black_row.append('td').text('Black');
black_row.append('td').text(`${black}`);
black_row.append('td').text(`${d3.format('0.2f')((100 * black) / total)}%`);
let other_row = table.append('tr');
other_row.append('td').text('Other');
other_row.append('td').text(`${total - white - black}`);
other_row
.append('td')
.text(`${d3.format('0.2f')((100 * (total - white - black)) / total)}%`);
table_map.set(state_fips + county_fips, div.node().innerHTML);
});
return table_map;
}
Insert cell
// Associate a color with each county fips code.
color_map = {
let color_map = new Map();
let max = d3.max(raw_data.map(o => parseFloat(o[1])));

raw_data.forEach(function([
place,
total,
white,
black,
state_fips,
county_fips
]) {
let population_density = total / area_map.get(state_fips + county_fips);
color_map.set(
state_fips + county_fips,
`rgba(${(255 * white) / total}, ${(255 * black) / total}, ${(255 *
(total - white - black)) /
total}, ${(population_density / max_density) ** 0.1})`
);
});
return color_map;
}
Insert cell
max_density = 0.028
// Comes from this computation:
// density_map = {
// let pop_data = (await (await fetch(
// 'https://api.census.gov/data/2019/pep/population?get=POP&for=county:*'
// )).json()).slice(1);
// let pop_map = new Map();
// pop_data.forEach(function([pop, state, county]) {
// pop_map.set(state + county, parseFloat(pop));
// });
// let density_map = new Map();
// for (let key of pop_map.keys()) {
// density_map.set(key, pop_map.get(key) / area_map.get(key));
// }
// density_map.max = d3.max(Array.from(density_map.values()));
// return density_map;
// }
Insert cell
area_map = {
let area_map = new Map();
map_data.counties.features
.map(c => c.properties)
.forEach(o => area_map.set(o.GEOID, o.ALAND));
return area_map;
}
Insert cell
// Load the TopoJSON map file
map_data = {
let map_file = await FileAttachment("AlbersUSA_county@1.json").json();
let counties = topojson.feature(map_file, map_file.objects.counties);
let states = topojson.feature(map_file, map_file.objects.states).features;
let nation = topojson.feature(map_file, map_file.objects.nation).features;
return {
nation: nation,
states: states,
counties: counties
};
}
Insert cell
Insert cell
import { Text } from '@observablehq/inputs'
Insert cell
tippy = require('tippy.js@6')
Insert cell
topojson = require("topojson-client@3")
Insert cell
//d3 = require('d3@6')
d3 = require('d3-selection@2', 'd3-array@2', 'd3-format@2', 'd3-geo@2', 'd3-zoom@2')
Insert cell
tippy_style = html`<link rel="stylesheet" href="${await require.resolve(
`tippy.js/themes/light.css`
)}">`
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more