function BarChart(data, {
x = (d, i) => i,
y = d => d,
marginTop = 20,
marginRight = 0,
marginBottom = 30,
marginLeft = 40,
width = 640,
height = 400,
xDomain,
xRange = [marginLeft, width - marginRight],
yType = d3.scaleLinear,
yDomain,
yRange = [height - marginBottom, marginTop],
xPadding = 0.1,
yFormat,
yLabel,
color = "currentColor",
duration: initialDuration = 250,
delay: initialDelay = (_, i) => i * 20
} = {}) {
const X = d3.map(data, x);
const Y = d3.map(data, y);
if (xDomain === undefined) xDomain = X;
if (yDomain === undefined) yDomain = [0, d3.max(Y)];
xDomain = new d3.InternSet(xDomain);
const I = d3.range(X.length).filter(i => xDomain.has(X[i]));
const xScale = d3.scaleBand(xDomain, xRange).padding(xPadding);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);
const format = yScale.tickFormat(100, yFormat);
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
const yGroup = svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick").call(grid))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel));
let rect = svg.append("g")
.attr("fill", color)
.selectAll("rect")
.data(I)
.join("rect")
.property("key", i => X[i])
.call(position, i => xScale(X[i]), i => yScale(Y[i]))
.style("mix-blend-mode", "multiply")
.call(rect => rect.append("title")
.text(i => [X[i], format(Y[i])].join("\n")));
const xGroup = svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis);
function position(rect, x, y) {
return rect
.attr("x", x)
.attr("y", y)
.attr("height", typeof y === "function" ? i => yScale(0) - y(i) : i => yScale(0) - y)
.attr("width", xScale.bandwidth());
}
function grid(tick) {
return tick.append("line")
.attr("class", "grid")
.attr("x2", width - marginLeft - marginRight)
.attr("stroke", "currentColor")
.attr("stroke-opacity", 0.1);
}
return Object.assign(svg.node(), {
update(data, {
xDomain,
yDomain,
duration = initialDuration,
delay = initialDelay
} = {}) {
const X = d3.map(data, x);
const Y = d3.map(data, y);
if (xDomain === undefined) xDomain = X;
if (yDomain === undefined) yDomain = [0, d3.max(Y)];
xDomain = new d3.InternSet(xDomain);
const I = d3.range(X.length).filter(i => xDomain.has(X[i]));
xScale.domain(xDomain);
yScale.domain(yDomain);
const t = svg.transition().duration(duration);
rect = rect
.data(I, function(i) { return this.tagName === "rect" ? this.key : X[i]; })
.join(
enter => enter.append("rect")
.property("key", i => X[i])
.call(position, i => xScale(X[i]), yScale(0))
.style("mix-blend-mode", "multiply")
.call(enter => enter.append("title")),
update => update,
exit => exit.transition(t)
.delay(delay)
.attr("y", yScale(0))
.attr("height", 0)
.remove()
);
rect.select("title")
.text(i => [X[i], format(Y[i])].join("\n"));
rect.transition(t)
.delay(delay)
.call(position, i => xScale(X[i]), i => yScale(Y[i]));
xGroup.transition(t)
.call(xAxis)
.call(g => g.selectAll(".tick").delay(delay));
yGroup.transition(t)
.call(yAxis)
.selection()
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick").selectAll(".grid").data([,]).join(grid));
}
});
}