Published
Edited
Jun 26, 2021
1 fork
Importers
21 stars
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

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more