function Sunburst(
data,
{
path,
id = Array.isArray(data) ? (d) => d.id : null,
parentId = Array.isArray(data) ? (d) => d.parentId : null,
children,
value,
sort = (a, b) => d3.descending(a.value, b.value),
label,
title,
link,
linkTarget = "_blank",
width = 640,
height = 400,
margin = 1,
marginTop = margin,
marginRight = margin,
marginBottom = margin,
marginLeft = margin,
padding = 1,
startAngle = 0,
endAngle = 2 * Math.PI,
radius = Math.min(
width - marginLeft - marginRight,
height - marginTop - marginBottom
) / 2,
color = d3.interpolateRainbow,
fill = "#ccc",
fillOpacity = 0.6
} = {}
) {
const root =
path != null
? d3.stratify().path(path)(data)
: id != null || parentId != null
? d3.stratify().id(id).parentId(parentId)(data)
: d3.hierarchy(data, children);
value == null ? root.count() : root.sum((d) => Math.max(0, value(d)));
if (sort != null) root.sort(sort);
d3.partition().size([endAngle - startAngle, radius])(root);
if (color != null) {
color = d3.scaleSequential([0, root.children.length], color).unknown(fill);
root.children.forEach((child, i) => (child.index = i));
}
const arc = d3
.arc()
.startAngle((d) => d.x0 + startAngle)
.endAngle((d) => d.x1 + startAngle)
.padAngle((d) => Math.min((d.x1 - d.x0) / 2, (2 * padding) / radius))
.padRadius(radius / 2)
.innerRadius((d) => d.y0)
.outerRadius((d) => d.y1 - padding);
const svg = d3
.create("svg")
.attr("viewBox", [
marginRight - marginLeft - width / 2,
marginBottom - marginTop - height / 2,
width,
height
])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "middle");
const cell = svg
.selectAll("a")
.data(root.descendants())
.join("a")
.attr("xlink:href", link == null ? null : (d) => link(d.data, d))
.attr("target", link == null ? null : linkTarget);
cell
.append("path")
.attr("d", arc)
.attr(
"fill",
color ? (d) => color(d.ancestors().reverse()[1]?.index) : fill
)
.attr("fill-opacity", fillOpacity);
if (label != null)
cell
.filter((d) => ((d.y0 + d.y1) / 2) * (d.x1 - d.x0) > 10)
.append("text")
.attr("transform", (d) => {
if (!d.depth) return;
const x = (((d.x0 + d.x1) / 2 + startAngle) * 180) / Math.PI;
const y = (d.y0 + d.y1) / 2;
return `rotate(${x - 90}) translate(${y},0) rotate(${
x < 180 ? 0 : 180
})`;
})
.attr("dy", "0.32em")
.text((d) => label(d.data, d));
if (title != null) cell.append("title").text((d) => title(d.data, d));
const stops = [
{ offset: "0%", opacity: 0 },
{ offset: "30%", opacity: 1 },
{ offset: "70%", opacity: 1 },
{ offset: "100%", opacity: 0 }
];
svg
.append("linearGradient")
.attr("id", "grayFadeX")
.selectAll("stop")
.data(stops)
.join("stop")
.attr("offset", (d) => d.offset)
.attr("stop-opacity", (d) => d.opacity)
.attr("stop-color", "white");
svg
.append("rect")
.style("mix-blend-mode", "saturation")
.attr("fill", "url(#grayFadeX)")
.attr("x", -200)
.attr("x", -200)
.attr("y", -200)
.attr("width", 500)
.attr("height", 500);
return svg.node();
}