Public
Edited
Sep 8, 2023
5 forks
8 stars
Insert cell
Insert cell
map = {
// Specify the dimensions of the chart.
const width = 975;
const height = 610;

const path = d3.geoPath();
const states = topojson.feature(us, us.objects.states);
const nodes = [];
const links = [];

// Compute the centroids of each (pre-projected) feature.
for (const f of states.features) {
if (f.id === "02" || f.id === "15" || f.id === "72") continue; // lower 48
const centroid = path.centroid(f);
if (centroid.some(isNaN)) return;
f.cx = f.x = centroid[0];
f.cy = f.y = centroid[1];
nodes.push(f);
}

// Compute the links from the Voronoi neighbors.
const delaunay = d3.Delaunay.from(nodes, (f) => f.x, (f) => f.y);
const voronoi = delaunay.voronoi([0, 0, width, height]);
for (let i = 0; i < nodes.length; i++) {
for (const j of voronoi.neighbors(i)) {
if (i < j) {
const source = nodes[i];
const target = nodes[j];
const dx = source.x - target.x;
const dy = source.y - target.y;
links.push({source, target, distance: Math.hypot(dx, dy)});
}
}
}

// Create a force simulation.
const simulation = d3.forceSimulation(nodes)
.alphaDecay(0)
.force("link", d3.forceLink(links).id((d) => d.id).distance((d) => d.distance))
// .force("x", d3.forceX((d) => d.cx))
// .force("y", d3.forceY((d) => d.cy))
.on("tick", ticked);

// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-100, -100, width + 200, height + 200])
.attr("style", "width: 100%; height: auto; overflow: visible;");

// Add a line for each link.
const link = svg.append("g")
.attr("stroke", "currentColor")
.attr("stroke-width", 1.5)
.selectAll("line")
.data(links)
.join("line");

// Add a path for each node.
const node = svg.append("g")
.attr("stroke", "#fff")
.attr("fill", "#777")
.attr("fill-opacity", 0.5)
.selectAll("path")
.data(nodes)
.join("path")
.attr("d", path);

// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Set the position attributes of links and nodes each time the simulation ticks.
function ticked() {
link
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);

node
.attr("transform", (d) => `translate(${d.x - d.cx},${d.y - d.cy})`);
}

// When drag starts, fix the subject position.
function dragstarted(event) {
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
event.subject.fx = null;
event.subject.fy = null;
}

// When this cell is re-run, stop the previous simulation. (This is
// especially important when setting the alpha decay to zero, such that
// the simulation won’t stop naturally.)
invalidation.then(() => simulation.stop());

return svg.node();
}
Insert cell
us = d3.json("https://cdn.jsdelivr.net/npm/us-atlas@3/states-albers-10m.json")
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