chart = {
const signs = new Map([].concat(
data.negatives.map(d => [d, -1]),
data.positives.map(d => [d, +1])
));
const bias = d3.sort(
d3.rollup(data, v => d3.sum(v, d => d.value * Math.min(0, signs.get(d.category))), d => d.name),
([, a]) => a
);
const width = 928;
const marginTop = 40;
const marginRight = 30;
const marginBottom = 0;
const marginLeft = 80;
const height = bias.length * 33 + marginTop + marginBottom;
const 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.rollup(data, data => d3.rollup(data, ([d]) => d.value, d => d.category), d => d.name));
const x = d3.scaleLinear()
.domain(d3.extent(series.flat(2)))
.rangeRound([marginLeft, width - marginRight])
const y = d3.scaleBand()
.domain(bias.map(([name]) => name))
.rangeRound([marginTop, height - marginBottom])
.padding(2 / 33)
const color = d3.scaleOrdinal()
.domain([].concat(data.negatives, data.positives))
.range(d3.schemeSpectral[data.negatives.length + data.positives.length])
const formatValue = ((format) => (x) => format(Math.abs(x)))(d3.format(".0%"));
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");
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")
.attr("transform", `translate(0,${marginTop})`)
.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));
svg.append("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)`));
return Object.assign(svg.node(), {scales: {color}});
}