Public
Edited
Jan 3, 2024
3 forks
50 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mouseOver = d => {
d3.selectAll(".link")
.style("stroke", linkDefault)
.style("opacity", fadeOpacity);

d3.selectAll(`.link-${d.state_abbr}`)
.raise()
.select(".link")
.style("stroke", linkStroke)
.style("opacity", 1);
d3.selectAll(".node")
.style("fill", nodeDefault)
.style("opacity", fadeOpacity);
d3.selectAll(`.node-${d.state_abbr}`)
.style("fill", "")
.style("opacity", 1);
}
Insert cell
mouseOut = _ => {
d3.selectAll(".link")
.style("stroke", linkStroke)
.style("opacity", 1);
d3.selectAll(".node")
.style("fill", "")
.style("opacity", 1);
}
Insert cell
fadeOpacity = 0.2;
Insert cell
Insert cell
linkDefault = "#ddd"
Insert cell
nodeDefault = "#aaa";
Insert cell
linkStroke = d => {
return d.diff === 2 ? `url(#${gradientPositive.id()})` :
d.diff === 1 ? `url(#${gradientNegative.id()})` :
linkDefault;
}
Insert cell
gradientPositive = gradientLinear()
.id("link-gradient-positive")
.offsets([10, 90])
.colors(["#F5D8F1", "#A23294"]);
Insert cell
gradientNegative = gradientLinear()
.id("link-gradient-negative")
.offsets([10, 90])
.colors(["#FDDCB8", "#C35F02"]);
Insert cell
style = `
.x.axis .tick text {
font-family: sans-serif;
font-size: 14px;
}
.x.axis .domain {
display: none;
}
.link {
fill: none;
stroke-width: 4px;
}
.link-bg {
fill: none;
stroke-width: 8px;
stroke: white;
}
.patch {
fill: white;
pointer-events: none;
}
.node {
font-family: monospace;
text-anchor: middle;
transform: translateY(5px);
fill: ${nodeDefault};
}
.node.increase {
fill: #732569;
}
.node.decrease {
fill: #9F4C00;
}
.voronoi {
fill-opacity: 0;
}
`
Insert cell
Insert cell
y = d3.scaleBand()
.domain(ranks)
.range([0, chartHeight]);
Insert cell
x = d3.scalePoint()
.domain(data.map(d => d.year))
.range([0, chartWidth]);
Insert cell
strokeWidthScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.pop))
.range([1, 12]);
Insert cell
Insert cell
line = d3.line()
.x(d => x(d.year))
.y(d => y(d.rank))
.curve(d3.curveBumpX);
Insert cell
Insert cell
rowHeight = 14;
Insert cell
patchWidth = 24;
Insert cell
margin = ({ left: 14, right: 18, top: 36, bottom: 20 });
Insert cell
chartWidth = width - margin.left - margin.right;
Insert cell
chartHeight = ranks.length * rowHeight;
Insert cell
Insert cell
Insert cell
voronoiData = Voronoi()
.x(d => x(d.year))
.y(d => y(d.rank))
.size([chartWidth, chartHeight])
(data);
Insert cell
linkData = data
.filter(({ year }) => year !== x.domain()[x.domain().length - 1])
.map(curr => {
const { rank, state_abbr, year } = curr;
const next = data.find(d => d.state_abbr === state_abbr && d.year === year + 10 );
const out = [ curr, next ];
out.diff = !next ? false :
rank === next.rank ? 0 :
rank < next.rank ? 1 :
2;
out.state_abbr = state_abbr;
return out;
})
.filter(([curr, next]) => next)
.sort((a, b) => d3.ascending(a.diff, b.diff));
Insert cell
ranks = d3.range(
...d3.extent(data, d => d.rank).map((d, i) => i === 0 ? d : d + 1)
);
Insert cell
data = {
let data = (await FileAttachment("apportionment.csv").csv())
.map(d => {
const lookup = stateLookup.find(d0 => d0.state_name === d.Name);
const obj = {};
obj.state_name = d.Name;
obj.state_abbr = lookup ? lookup.state_postal : "";
obj.pop = +keepNumber(d["Resident Population"]);
obj.year = +d.Year;
return obj;
})
.filter(d => d.state_abbr);

const data2020 = (await FileAttachment("census-data.json").json()).states
.filter(d => d.year === 2020)
.map(d => {
const obj = {};
obj.state_name = d.geo_name;
obj.state_abbr = d.stab;
obj.pop = d.population;
obj.year = d.year;
return obj;
});
data = [...data, ...data2020];
const out = [];
const years = d3.groups(data, d => d.year);
years.forEach(([year, entries]) => {
entries.sort((a, b) => d3.descending(a.pop, b.pop)).forEach((d, i) => {
d.rank = i + 1;
out.push(d);
});
});
return out.filter(d => d.year >= startYear);
}
Insert cell
function keepNumber(x){
return x.replace(/[^\d.-]/g, "");
}
Insert cell
stateLookup = FileAttachment("states.csv").csv();
Insert cell
Insert cell
import { Voronoi } from "@harrystevens/voronoi";
Insert cell
import { gradientLinear } from "@washpostgraphics/gradient-generators";
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