boxplot = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const g = svg
.append("g")
.selectAll("g")
.data(bins)
.join("g");
g.append("path")
.attr("stroke", "currentColor")
.attr(
"d",
d => `
M${x_scale((d.x0 + d.x1) / 2)},${y_scale(d.range[1])}
V${y_scale(d.range[0])}
`
);
g.append("path")
.attr("fill", "#ddd")
.attr(
"d",
d => `
M${x_scale(d.x0) + 1},${y_scale(d.quartiles[2])}
H${x_scale(d.x1)}
V${y_scale(d.quartiles[0])}
H${x_scale(d.x0) + 1}
Z
`
);
g.append("path")
.attr("stroke", "currentColor")
.attr("stroke-width", 2)
.attr(
"d",
d => `
M${x_scale(d.x0) + 1},${y_scale(d.quartiles[1])}
H${x_scale(d.x1)}
`
);
g.append("g")
.attr("fill-opacity", 0.5)
.attr("stroke", "none")
.attr("transform", d => `translate(${x_scale((d.x0 + d.x1) / 2)},0)`)
.selectAll("circle")
.data(d => d.outliers)
.join("circle")
.attr("r", 2)
.attr("fill", d => color(d.color))
.attr("cx", () => (Math.random() - 0.5) * 4)
.attr("cy", d => y_scale(d.y));
svg.append("g").call(x_axis);
svg.append("g").call(y_axis);
svg
.append("text")
.attr("x", width / 2)
.attr("y", height - 10)
.style("text-anchor", "middle")
.text(x_variable);
svg
.append("text")
.attr("class", "y label")
.attr("text-anchor", "middle")
.attr("y", 15)
.attr("x", -height / 2)
.attr("transform", "rotate(-90)")
.text(y_variable);
svg
.append("text")
.attr("x", width / 2)
.attr("y", 40)
.style("text-anchor", "middle")
.text(title);
return svg.node();
}