Published
Edited
Apr 11, 2022
1 fork
Importers
3 stars
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

svg.append("g").call(xAxis);

svg.append("g").call(yAxis);

svg
.append("path")
.attr("d", `M ${x(0)} ${y(0)} L ${x(1)} ${y(1)}`)
.attr("stroke", "#ccc")
.attr("stroke-dasharray", "3 4");

svg
.append("g")
.attr("fill", "none")
.selectAll("path")
.data(groups)
.join("path")
.attr("d", d => line(d[1].curve))
.attr("stroke", d => color(d[1].gini))
.call(halo);

return svg.node();
}
Insert cell
gini = arr =>
d3.sum(d3.cross(arr, arr, (a, b) => Math.abs(a - b))) /
(2 * arr.length ** 2 * d3.mean(arr))
Insert cell
// Table A4, “Mean Household Income of Quintiles” from
// https://www.census.gov/data/tables/2019/demo/income-poverty/p60-266.html
data = {
const csv = d3.csvParse(
await FileAttachment("usa gini - table a4.csv").text(),
d3.autoType
);
const [variable, ...rest] = csv.columns;
return csv.flatMap(quintile =>
rest.map(year => ({
group: year,
item: quintile[variable],
value: quintile[year]
}))
);
}
Insert cell
groups = d3.rollups(
data,
d => {
const sorted = d.map(d => d.value).sort(d3.ascending);
return {
curve: [0, ...d3.cumsum(sorted)].map((d, i, arr) => [
i / (arr.length - 1),
d / arr[arr.length - 1]
]),
gini: gini(sorted)
};
},
d => d.group
)
Insert cell
xTitle = "Percentage of items"
Insert cell
yTitle = "Percentage of total value"
Insert cell
x = d3.scaleLinear().range([margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear().range([height - margin.bottom, margin.top])
Insert cell
// You could smooth with `.curve(d3.curveMonotoneX)`
// but it may mislead, making coarse data look too fine.
line = d3
.line()
.x(d => x(d[0]))
.y(d => y(d[1]))
// .curve(d3.curveMonotoneX)
Insert cell
xAxis = g =>
g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(
d3
.axisBottom(x)
.ticks(width / 80)
.tickFormat(pct)
)
.call(g => g.select(".domain").remove())
.call(g =>
g
.append("text")
.attr("x", width)
.attr("y", margin.bottom - 4)
.attr("fill", "currentColor")
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.text(xTitle)
)
Insert cell
yAxis = g =>
g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).tickFormat(pct))
.call(g => g.select(".domain").remove())
.call(g =>
g
.append("text")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(yTitle)
)
Insert cell
color = d3.scaleSequential(
d3.extent(groups.map(d => d[1].gini)),
d3.interpolateWarm
)
Insert cell
margin = ({top: 25, right: 20, bottom: 35, left: 40})
Insert cell
height = width
Insert cell
pct = d3.format(".0%")
Insert cell
// from https://observablehq.com/@d3/connected-scatterplot
// notice that this introduces a distortion: lines drawn later are more prominent
function halo(path) {
path
.select(function() {
return this.parentNode.insertBefore(this.cloneNode(true), this);
})
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 4)
.attr("stroke-linejoin", "round");
}
Insert cell
d3 = require("d3@5", "d3-array@2")
Insert cell
import { legend } from "@d3/color-legend"
Insert cell
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