The impact of vaccines
A recreation of a WSJ graphic by Tynan DeBold and Dov Friedman.
// Declare the chart dimensions and margins.
const marginTop = 20;
const marginRight = 1;
const marginBottom = 40;
const marginLeft = 40;
const rowHeight = 14;
const width = 960;
const height = rowHeight * names.length + marginTop + marginBottom;
// Create a number format.
const f = d3.format(",d");
const format = (d) => isNaN(d) ? "N/A cases"
: d === 0 ? "0 cases"
: d < 1 ? "<1 case"
: d < 1.5 ? "1 case"
: `${f(d)} cases`;
// Create the scales.
const x = d3.scaleLinear()
.domain([d3.min(years), d3.max(years) + 1])
.rangeRound([marginLeft, width - marginRight])
const y = d3.scaleBand()
.domain(names)
.rangeRound([marginTop, height - marginBottom])
const color = d3.scaleSequentialSqrt()
.domain([0, d3.max(values, (d) => d3.max(d))])
.interpolator(d3.interpolatePuRd);
// Create the SVG container.
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickSize(0))
.call((g) => g.select(".domain").remove());
// Create a cell for each (state, year) value.
svg.append("g")
.selectAll("g")
.data(values)
.join("g")
.attr("transform", (d, i) => `translate(0,${y(names[i])})`)
.selectAll("rect")
.data((d) => d)
.join("rect")
.attr("x", (d, i) => x(years[i]) + 1)
.attr("width", (d, i) => x(years[i] + 1) - x(years[i]) - 1)
.attr("height", y.bandwidth() - 1)
.attr("fill", (d) => isNaN(d) ? "#eee" : d === 0 ? "#fff" : color(d))
.append("title")
.text((d, i) => `${format(d)} per 100,000 people in ${years[i]}`);
// Append the axes.
svg.append("g")
.call((g) => g.append("g")
.attr("transform", `translate(0,${marginTop})`)
.call(d3.axisTop(x).ticks(null, "d"))
.call((g) => g.select(".domain").remove()))
.call((g) => g.append("g")
.attr("transform", `translate(0,${height - marginBottom + 4})`)
.call(d3.axisBottom(x)
.tickValues([year])
.tickFormat((x) => x)
.tickSize(marginTop + marginBottom - height - 10))
.call((g) => g.select(".tick text")
.clone()
.attr("dy", "2em")
.style("font-weight", "bold")
.text("Measles vaccine introduced"))
.call((g) => g.select(".domain").remove()));
display(svg.node());
const names = ["Alaska", "Ala.", "Ark.", "Ariz.", "Calif.", "Colo.", "Conn.", "D.C.", "Del.", "Fla.", "Ga.", "Hawaii", "Iowa", "Idaho", "Ill.", "Ind.", "Kan.", "Ky.", "La.", "Mass.", "Md.", "Maine", "Mich.", "Minn.", "Mo.", "Miss.", "Mont.", "N.C.", "N.D.", "Neb.", "N.H.", "N.J.", "N.M", "Nev.", "N.Y.", "Ohio", "Okla.", "Ore.", "Pa.", "R.I.", "S.C.", "S.D.", "Tenn.", "Texas", "Utah", "Va.", "Vt.", "Wash.", "Wis.", "W.Va.", "Wyo."];
const diseases = await FileAttachment("data/vaccines.json").json();
const [measles] = diseases;
const values = [];
const year0 = d3.min(measles.data.values.data, (d) => d[0]);
const year1 = d3.max(measles.data.values.data, (d) => d[0]);
const years = d3.range(year0, year1 + 1);
const year = measles.data.chart_options.vaccine_year;
for (const [year, i, value] of measles.data.values.data) {
if (value == null) continue;
(values[i] || (values[i] = []))[year - year0] = value;
}
display({values, years, year});