chart = () => {
const wrapper = d3.create("div");
wrapper.append("style").html(css);
const chart = wrapper.selectAll(".chart")
.data(data)
.join("div")
.attr("class", "chart");
const title = chart.append("div")
.attr("class", "title")
.html(d => titles[d.prop]);
const svg = chart.append("svg");
const g = svg.append("g");
const xaxis = g.append("g")
.attr("class", "axis x-axis");
const yaxis = g.append("g")
.attr("class", "axis y-axis");
const bar = g.append("g")
.attr("class", "bars")
.selectAll(".bar")
.data(d => d.data)
.join("rect")
.attr("class", "bar");
const trend = g.append("polyline")
.attr("class", "trend");
const trendLabel = g.append("text")
.attr("class", "trend-label")
.attr("dy", -4)
.text(d => d.prop === "normalized" ? "No trend" : "Increase");
const anno = g.append("g")
.attr("class", "anno")
.attr("text-anchor", d => annoData[d.prop].textAnchor);
const annoLeader = anno.append("polyline")
.attr("class", "anno-leader");
const annoYear = anno.append("text")
.attr("class", "anno-year")
.attr("y", 5)
.text(d => annoData[d.prop].year)
const annoText = anno.append("text")
.attr("class", "anno-text")
.attr("y", 23);
return Object.assign(wrapper.node(), {
resize(ww) {
// Resize: Dimensions
const cols = ww <= 560 ? 1 : 2;
const rows = data.length / cols;
const padInner = 24;
const padTotal = padInner * (cols - 1);
const pad = padTotal / cols;
const r = ww <= 400 ? 3 : 4;
const margin = { left: 51, right: 14, top: 7, bottom: 23 };
const chartMarginBottom = 24;
const basewidth = ww / cols - pad;
const width = basewidth - margin.left - margin.right;
const height = Math.max(300, basewidth * 9 / 16) - margin.top - margin.bottom;
// Resize: Scales
x.range([0, width]);
props.forEach(prop => {
Y[prop].range([height, 0]);
});
// Resize: Scaffold
chart
.style("margin-bottom", (_, i) => 1 + Math.floor(i / cols) === rows ? "0px" : `${chartMarginBottom}px`)
.style("margin-left", (_, i) => i % cols === 0 ? "0px" : `${padInner / 2}px`)
.style("margin-right", (_, i) => i % cols === (cols - 1) ? "0px" : `${padInner / 2}px`)
.style("width", `calc(${100 / cols}% - ${pad}px)`)
svg
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
g
.attr("transform", `translate(${[margin.left, margin.top]})`);
// Resize: Axes
xaxis
.attr("transform", `translate(0, ${height})`)
.call(g => xAxisGenerator(g, height));
yaxis
.attr("transform", `translate(${width})`)
.each((d, i, e) => {
d3.select(e[i]).call(g => yAxisGenerator(g, d.prop, width, Y))
});
// Resize: Marks
bar
.attr("height", d => height - Y[d.prop](d.value))
.attr("width", x.bandwidth())
.attr("x", d => x(d.year))
.attr("y", d => Y[d.prop](d.value));
trend
.attr("points", d => {
return regressionToLine(d);
});
// Resize: Labels
const i = {
"inflation": 0.55,
"normalized": 0.67
}
trendLabel
.attr("transform", d => {
const l = regressionToLine(d);
const a = lineAngle(l);
const p = lineInterpolate(l)(i[d.prop]);
return `translate(${p}) rotate(${a})`
});
anno
.attr("transform", d => {
const { year } = annoData[d.prop]
const { value } = d.data.find(f => f.year === year);
return `translate(${x(year) + x.bandwidth() / 2}, ${Y[d.prop](value)})`;
});
annoLeader
.attr("points", d => {
const offset = annoData[d.prop].offset(ww);
if (offset < 0) {
return [[-x.bandwidth() / 2 + offset, 0], [x.bandwidth() / 2, 0]]
}
else {
return [[-x.bandwidth() / 2, 0], [x.bandwidth() / 2 + offset, 0]]
}
})
annoYear
.attr("x", d => annoOffset(d));
annoText
.attr("transform", d => `translate(${annoOffset(d)})`)
.html(d => {
return annoData[d.prop].html(ww)
})
.selectAll("tspan")
.attr("x", 0)
.attr("dy", (d, i) => i === 0 ? 0 : 16)
}
})
}