Published unlisted
Edited
Jan 18, 2022
1 fork
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

svg.append("g")
.selectAll("g")
.data(series)
.join("g")
.attr("fill", d => color(d.key))
.selectAll("rect")
.data(d => d.map(v => Object.assign(v, {key: d.key})))
.join("rect")
.attr("x", d => x(d[0]))
.attr("y", ({data: [name]}) => y(name))
.attr("width", d => x(d[1]) - x(d[0]))
.attr("height", y.bandwidth())
.append("title")
.text(({key, data: [name, value]}) => `${name}
${formatValue(value.get(key))} ${key}`);

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

svg.append("g")
.call(yAxis);
return svg.node();
}
Insert cell
data = {
const categories = {
"A - C : Wasichana": "A - C : Wasichana",
"A - C : Wavulana": "A - C : Wavulana",
"D : Wasichana": "D : Wasichana",
"D : Wavulana": "D : Wavulana",
"F : Wasichana": "F : Wasichana",
"F : Wavulana": "F : Wavulana",
"X : Wasichana": "X : Wasichana",
"X : Wavulana": "X : Wavulana"
};

const data = d3.csvParse(await FileAttachment("necta2021@1.csv").text(), ({speaker: name, ruling: category, count: value}) => categories[category] ? {name, category: categories[category], value: +value} : null);

// Normalize absolute values to percentage.
d3.rollup(data, group => {
const sum = d3.sum(group, d => d.value);
for (const d of group) d.value /= sum;
}, d => d.name);

return Object.assign(data, {
format: ".0%",
negative: "← Wavulana",
positive: "Wasichana →",
negatives: [ "A - C : Wavulana","D : Wavulana","F : Wavulana", "X : Wavulana"],
positives: ["X : Wasichana","F : Wasichana","D : Wasichana","A - C : Wasichana"]
});
}
Insert cell
signs = new Map([].concat(
data.negatives.map(d => [d, -1]),
data.positives.map(d => [d, +1])
))
Insert cell
bias = d3.rollups(data, v => d3.sum(v, d => d.value * Math.min(0, signs.get(d.category))), d => d.name)
.sort(([, a], [, b]) => d3.ascending(a, b))
Insert cell

series = d3.stack()
.keys([].concat(data.negatives.slice().reverse(), data.positives))
.value(([, value], category) => signs.get(category) * (value.get(category) || 0))
.offset(d3.stackOffsetDiverging)
(d3.rollups(data, data => d3.rollup(data, ([d]) => d.value, d => d.category), d => d.name))

Insert cell
x = d3.scaleLinear()
.domain(d3.extent(series.flat(2)))
.rangeRound([margin.left, width - margin.right])
Insert cell
y = d3.scaleBand()
.domain(bias.map(([name]) => name))
.rangeRound([margin.top, height - margin.bottom])
.padding(2 / 33)
Insert cell
color = d3.scaleOrdinal()
.domain([].concat(data.negatives, data.positives))
.range(d3.schemeSpectral[11])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${margin.top})`)
.call(d3.axisTop(x)
.ticks(width / 80)
.tickFormat(formatValue)
.tickSizeOuter(0))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", x(0) + 20)
.attr("y", -24)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(data.positive))
.call(g => g.append("text")
.attr("x", x(0) - 20)
.attr("y", -24)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(data.negative))
Insert cell
yAxis = g => g
.call(d3.axisLeft(y).tickSizeOuter(0))
.call(g => g.selectAll(".tick").data(bias).attr("transform", ([name, min]) => `translate(${x(min)},${y(name) + y.bandwidth() / 2})`))
.call(g => g.select(".domain").attr("transform", `translate(${x(0)},0)`))
Insert cell
formatValue = {
const format = d3.format(data.format || "");
return x => format(Math.abs(x));
}
Insert cell
height = bias.length * 33 + margin.top + margin.bottom
Insert cell
margin = ({top: 40, right: 30, bottom: 0, left: 80})
Insert cell
d3 = require("d3@6")
Insert cell
import {swatches} from "@d3/color-legend"
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