Public
Edited
Mar 13, 2022
68 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
map1 = createBaseMap()
Insert cell
Insert cell
map2 = {
const map = createBaseMap();
d3.select(map)
.append("g")
.classed("centroids", true)
.selectAll("circle")
.data(states.features.map(d => d.properties))
.join("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", NODE.MIN_RADIUS)
.attr("fill", "blue");
return map;
}
Insert cell
Insert cell
createStatePacks = (data) => {
const statesPacked = new Map();

for (let [k, v] of data) {
v.sort((a, b) => (b.total_households - a.total_households)); // step 0
v = v.map(d => ({ data: d, r: radius(d.total_households) })); // step 1
const nodes = packSiblings(v) // step 1
const { r } = packEnclose(nodes) // step 2
const state = states.features.find(d => d.properties.name === k); // step 3
const {x, y} = state.properties; // step 3
statesPacked.set(k, { nodes, r, x, y }); // step 4
}
return statesPacked;
}
Insert cell
Insert cell
map3 = {
const map = createBaseMap();
const svg = d3.select(map);
const statesPacked = createStatePacks(dataByState);
d3.select(map)
.append("g")
.classed("centroids", true)
.selectAll("circle")
.data([...statesPacked])
.join("circle")
.attr("cx", ([k, d]) => d.x)
.attr("cy", ([k, d]) => d.y)
.attr("r", ([k, d]) => d.r)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width", 1);
return map;
}
Insert cell
Insert cell
applySimulation = (nodes) => {
const simulation = d3.forceSimulation(nodes)
.force("cx", d3.forceX().x(d => width / 2).strength(0.02))
.force("cy", d3.forceY().y(d => height / 2).strength(0.02))
.force("x", d3.forceX().x(d => d.x).strength(0.3))
.force("y", d3.forceY().y(d => d.y).strength(0.3))
.force("charge", d3.forceManyBody().strength(-1))
.force("collide", d3.forceCollide().radius(d => d.r + NODE.PADDING).strength(1))
.stop()

while (simulation.alpha() > 0.01) {
simulation.tick();
}

return simulation.nodes();
}
Insert cell
map4 = {
const map = createBaseMap();
const statesPacked = createStatePacks(dataByState);
let values = [...new Map(statesPacked).values()];
values = applySimulation(values)
d3.select(map)
.append("g")
.classed("centroids", true)
.selectAll("circle")
.data(values)
.join("circle")
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("r", (d) => d.r)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width", 1)

return map;
}
Insert cell
Insert cell
map5 = {
const map = createBaseMap()
const svg = d3.select(map);
const statesPacked = createStatePacks(dataByState);
let values = [...new Map(statesPacked).values()];
values = applySimulation(values)
svg.select(".state-boundaries")
.attr("stroke", "#fff");
const statePacks = svg
.append("g")
.classed("state-packs", true)
.selectAll(".state-pack")
.data(values)
.enter()
.append("g")
.classed("state-pack", true)
.attr("transform", d => `translate(${d.x}, ${d.y})`);

statePacks
.append("circle")
.attr("r", d => d.r)
.attr("fill", "#e2e2e2")
.attr("stroke", "#333");

const counties = statePacks
.selectAll(".county-centroid")
.data(d => d.nodes)
.enter()
.append("circle")
.classed("county-centroid", true)
.attr("r", d => d.r)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("fill", d => color(withBroadbandPct(d.data)))
counties.append("title")
.text(d => `${d.data.county}, ${d.data.state}\nTotal Households: ${d.data.total_households.toLocaleString()}\nWith broadband subscription: ${d3.format(".2f")(withBroadbandPct(d.data) * 100)}%`)
return map;
}
Insert cell
Insert cell
height = 625;
Insert cell
NODE = ({ MIN_RADIUS: 2.5, MAX_RADIUS: 20, PADDING: 2 });
Insert cell
packSiblings = (values) => d3.packSiblings(values)
Insert cell
packEnclose = (nodes) => d3.packEnclose(nodes)
Insert cell
scheme = d3.schemeGnBu
Insert cell
color = d3.scaleQuantize()
.domain([0, 1])
.range(scheme[9])
Insert cell
radius = d3.scaleSqrt()
.domain(d3.extent(data, d => d.total_households))
.range([NODE.MIN_RADIUS, NODE.MAX_RADIUS])
Insert cell
geoPath = d3.geoPath()
Insert cell
createBaseMap = () => {
const svg = d3.create("svg")
.attr("viewBox", `0 0 960 ${height}`)
.style("width", "100%")
.style("height", "auto")

svg.append("rect")
.attr("width", 960)
.attr("height", height)
.attr("fill", "white");
svg.append("path")
.classed("state-boundaries", true)
.datum(stateBoundaries)
.attr("fill", "none")
.attr("stroke", "lightgray")
.attr("stroke-width", 1)
.attr("stroke-linejoin", "round")
.attr("d", geoPath);

svg.append("path")
.classed("nation-boundary", true)
.datum(nation)
.attr("fill", "none")
.attr("stroke", "gray")
.attr("stroke-linejoin", "round")
.attr("d", geoPath);
return svg.node();
}
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
withBroadbandPct = d => {
return d.with_broadband_subscription / d.total_households;
}
Insert cell
html`<style>
svg .county-centroid:hover {
fill: #333;
}
</style>`
Insert cell
Insert cell
dataByState = d3.group(data, d => d.state)
Insert cell
data = d3.csv("https://gist.githubusercontent.com/clhenrick/5fe3463586ee196820ac74a62fd0445a/raw/09422f3803f59e5530836b1614774c5a7a9381f3/acs5_2017_internet_access_joined_rural_pct_names.csv", parseRow)
Insert cell
parseRow = (row) => {
for (let key in row) {
if (key === "name") {
const [county, state] = row.name.split(", ");
row.state = state;
row.county = county;
delete row.name;
} else if (key !== "geoid") {
row[key] = Number(row[key]);
}
}
return row;
}
Insert cell
nation = topojson.mesh(us, us.objects.nation)
Insert cell
states = {
const states = topojson.feature(us, us.objects.states);
states.features.forEach(feature => {
const [x, y] = geoPath.centroid(feature)
feature.properties = {...feature.properties, x, y};
});
return states;
}
Insert cell
stateBoundaries = topojson.mesh(us, us.objects.states, (a, b) => a !== b)
Insert cell
counties = topojson.feature(us, us.objects.counties);
Insert cell
us = d3.json("https://unpkg.com/us-atlas@2/us/10m.json")
Insert cell
Insert cell
topojson = require("topojson-client@3")
Insert cell
d3 = require("d3@5", "d3-array@2")
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