function Pack(
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 = 3,
fill = (d) => "#fde68a",
fillOpacity,
stroke = "#334155",
strokeWidth = 1,
strokeOpacity
} = {}
) {
const root =
path != null
? d3.stratify().path(path)(data)
: id != null || parentId != null
? d3.stratify().id(id).parentId(parentId)(data)
: d3.hierarchy(data, children);
// Compute the values of internal nodes by aggregating from the leaves.
value == null ? root.count() : root.sum((d) => Math.max(0, value(d)));
// Compute labels and titles.
const descendants = root.descendants();
const leaves = descendants.filter((d) => !d.children);
leaves.forEach((d, i) => (d.index = i));
const L = label == null ? null : leaves.map((d) => label(d.data, d));
const T = title == null ? null : descendants.map((d) => title(d.data, d));
// Sort the leaves (typically by descending value for a pleasing layout).
if (sort != null) root.sort(sort);
// Compute the layout.
d3
.pack()
.size([width - marginLeft - marginRight, height - marginTop - marginBottom])
.padding(padding)(root);
const svg = d3
.create("svg")
.attr("viewBox", [-marginLeft, -marginTop, 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", 5)
.attr("text-anchor", "middle");
const node = svg
.selectAll("a")
.data(descendants)
.join("a")
.attr("transform", (d) => `translate(${d.x},${d.y})`);
node
.append("circle")
.attr("fill", (d) => (d.children ? "#fff" : fill(d)))
.attr("fill-opacity", (d) => (d.children ? null : fillOpacity))
.attr("stroke", (d) => (d.parent && d.children ? stroke : null))
.attr("stroke-width", (d) => (d.children ? strokeWidth : null))
.attr("stroke-opacity", (d) => (d.children ? strokeOpacity : null))
.attr("r", (d) => d.r);
if (T) node.append("title").text((d, i) => T[i]);
if (L) {
// A unique identifier for clip paths (to avoid conflicts).
const uid = `O-${Math.random().toString(16).slice(2)}`;
const leaf = node.filter(
(d) => !d.children && d.r > 10 && L[d.index] != null
);
leaf
.append("clipPath")
.attr("id", (d) => `${uid}-clip-${d.index}`)
.append("circle")
.attr("r", (d) => d.r);
leaf
.append("text")
.attr(
"clip-path",
(d) => `url(${new URL(`#${uid}-clip-${d.index}`, location)})`
)
.selectAll("tspan")
.data((d) => `${L[d.index]}`.split(/\n/g))
.join("tspan")
.attr("x", 0)
.attr("y", (d, i, D) => `${i - D.length / 2 + 0.85}em`)
.attr("fill-opacity", (d, i, D) => (i === D.length - 1 ? 0.7 : null))
.text((d) => d);
}
const mainCategories = node.filter((d) => d.parent === root);
mainCategories
.append("text")
.attr("transform", (d) => `translate(${0},${d.r + 20})`)
.attr("font-size", "1.4em")
.text((d) => d.data.name);
return svg.node();
}