function circleLegend({
scale,
fill = "none",
stroke = "black",
strokeWidth = 1,
marginTop = 5,
marginRight = 50,
marginBottom = 10,
marginLeft = 1,
ticks = 3,
tickSize = 5,
tickFormat = (d) => d,
tickValues,
tickStroke = "currentColor",
tickStrokeDash = "4, 2",
tickStrokeWidth = 1,
tickFont = "12px sans-serif",
tickWrap = false,
tickWrapSpacing = 12
} = {}) {
ticks = tickValues || scale.ticks(ticks);
const r = scale(d3.max(ticks));
const width = r * 2 + marginRight;
const height = r * 2 + marginBottom;
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("overflow", "visible");
svg
.selectAll("circle")
.data(ticks)
.join("circle")
.sort((a, b) => b - a)
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("cx", r)
.attr("cy", scale)
.attr("r", scale);
svg
.selectAll("line")
.data(ticks)
.join("line")
.attr("stroke", tickStroke)
.attr("stroke-width", tickStrokeWidth)
.attr("stroke-dasharray", tickStrokeDash)
.attr("x1", r)
.attr("x2", tickSize + r * 2)
.attr("y1", (d) => scale(d) * 2)
.attr("y2", (d) => scale(d) * 2);
const ticksFormatted = ticks
.map((d, i, g) => ({ d, text: tickFormat(d, i, g) }))
.map((d) => ({
...d,
lines: getLines(d.text, marginRight - (tickSize + 3), tickFont)
}));
const tickLabels = svg
.selectAll("text")
.data(ticksFormatted)
.join("text")
.attr(
"transform",
({ d }) => `translate(${r * 2 + tickSize + 3}, ${scale(d) * 2})`
)
.style("font", tickFont);
if (tickWrap) {
tickLabels
.selectAll("tspan")
.data((d) => d.lines)
.join("tspan")
.attr("x", 0)
.attr("y", (d, i, g) => (i - g.length / 2 + 0.8) * tickWrapSpacing)
.text((d) => d.text);
} else {
tickLabels.attr("dominant-baseline", "middle").text((d) => d.text);
}
return Object.assign(svg.node(), { dimensions: { height, width } });
}