function Histogram(data, {
value = d => d,
domain,
label,
format,
type = d3.scaleLinear,
x = value,
y = () => 1,
thresholds = 40,
normalize,
marginTop = 20,
marginRight = 30,
marginBottom = 30,
marginLeft = 40,
width = 640,
height = 400,
insetLeft = 0.5,
insetRight = 0.5,
xType = type,
xDomain = domain,
xRange = [marginLeft, width - marginRight],
xLabel = label,
xFormat = format,
yType = d3.scaleLinear,
yDomain,
yRange = [height - marginBottom, marginTop],
yLabel = "↑ Frequency",
yFormat = normalize ? "%" : undefined,
color = "currentColor"
} = {}) {
const X = d3.map(data, x);
const Y0 = d3.map(data, y);
const I = d3.range(X.length);
// Compute bins.
const bins = d3.bin().thresholds(thresholds).value(i => X[i])(I);
const Y = Array.from(bins, I => d3.sum(I, i => Y0[i]));
if (normalize) {
const total = d3.sum(Y);
for (let i = 0; i < Y.length; ++i) Y[i] /= total;
}
// Compute default domains.
if (xDomain === undefined) xDomain = [bins[0].x0, bins[bins.length - 1].x1];
if (yDomain === undefined) yDomain = [0, d3.max(Y)];
// Construct scales and axes.
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).ticks(width / 80, xFormat).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);
yFormat = 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;");
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel));
svg.append("g")
.attr("fill", color)
.selectAll("rect")
.data(bins)
.join("rect")
.attr("x", d => xScale(d.x0) + insetLeft)
.attr("width", d => Math.max(0, xScale(d.x1) - xScale(d.x0) - insetLeft - insetRight))
.attr("y", (d, i) => yScale(Y[i]))
.attr("height", (d, i) => yScale(0) - yScale(Y[i]))
.append("title")
.text((d, i) => [`${d.x0} ≤ x < ${d.x1}`, yFormat(Y[i])].join("\n"));
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
.call(g => g.append("text")
.attr("x", width - marginRight)
.attr("y", 27)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(xLabel));
return svg.node();
}