function TreeValue(
data,
{
path,
id = Array.isArray(data) ? (d) => d.id : null,
parentId = Array.isArray(data) ? (d) => d.parentId : null,
children,
format = ",",
value = (d) => d.value,
sort = (a, b) => d3.descending(a.value, b.value),
label,
title,
link,
linkTarget = "_blank",
width = 640,
height = 400,
margin = 0,
marginTop = margin,
marginRight = margin,
marginBottom = margin,
marginLeft = margin,
padding = 1,
round = false,
colorScheme = d3.interpolateSpectral,
color = null,
fill = "#ccc",
fillOpacity = 1,
stroke = "#555",
strokeWidth = 1.5,
strokeOpacity = 0.4,
strokeLinejoin,
strokeLinecap,
halo = "#fff",
haloWidth = 3,
parentR = 3,
drawLayout = false,
minHeightForLabel = 1,
fontSizeRange = [1, 25]
} = {}
) {
let root =
path != null
? d3.stratify().path(path)(data)
: id != null || parentId != null
? d3.stratify().id(id).parentId(parentId)(data)
: d3.hierarchy(data, children).sort(sort);
value == null ? root.count() : root.sum((d) => Math.max(0, value(d)));
if (typeof format !== "function") format = d3.format(format);
if (sort != null) root.sort(sort);
d3
.partition()
.size([height - marginTop - marginBottom, width - marginLeft - marginRight])
.padding(padding)
.round(round)(root);
if (!color) {
const colorScale = d3
.scaleSequential([0, root.children.length - 1], colorScheme)
.unknown(fill);
root.children.forEach((child, i) => (child.index = i));
color = (d) => colorScale(d.ancestors().reverse()[1]?.index);
}
const descendants = root.descendants();
let rScale = d3
.scaleLinear()
.domain([0, d3.max(descendants, (d) => d?.value)])
.range([0.1, d3.max(descendants, (d) => (d.x1 - d.x0) / 2) - 1]);
let fontScale = d3.scaleLinear().domain([0, 20]).range(fontSizeRange);
const svg = d3
.create("svg")
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("width", width)
.attr("height", height)
.attr("overflow", "visible")
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
if (drawLayout) {
svg
.append("g")
.selectAll("rect")
.data(descendants)
.join("rect")
.attr("y", (d) => d.x0)
.attr("x", (d) => d.y0)
.attr("height", (d) => d.x1 - d.x0)
.attr("width", (d) => d.y1 - d.y0)
.attr("fill", "none")
.attr("stroke", stroke)
.attr("stroke-opacity", strokeOpacity)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth);
}
svg
.append("g")
.attr("fill", "none")
.attr("stroke", stroke)
.attr("stroke-opacity", strokeOpacity)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth)
.selectAll("path")
.data(root.links())
.join("path")
.attr(
"d",
d3
.linkHorizontal()
.x((d) => d.y0 + (d.y1 - d.y0) / 2)
.y((d) => d.x0 + (d.x1 - d.x0) / 2)
);
const cell = svg
.append("g")
.selectAll("a")
.data(descendants)
.join("a")
.attr("xlink:href", link == null ? null : (d) => link(d.data, d))
.attr("target", link == null ? null : linkTarget)
.attr(
"transform",
(d) => `translate(${d.y0 + +(d.y1 - d.y0) / 2},${d.x0})`
);
cell
.append("circle")
.attr("r", (d) => (!d.children ? rScale(d.value) : parentR))
.attr("cy", (d) => rScale(d.value))
.attr("fill", (d) => (!d.children ? (color ? color(d) : fill) : "none"))
.attr("stroke", (d) => (d.children ? (color ? color(d) : fill) : "none"))
.attr("fill-opacity", fillOpacity);
const text = cell
.filter((d) => d.x1 - d.x0 > minHeightForLabel)
.append("text")
.attr("text-anchor", (d) => (d.children ? "end" : "start"))
.attr("x", (d) => (!d.children ? rScale(d.value) + 2 : -parentR - 2))
.attr("y", (d) => (d.x1 - d.x0) / 2)
.attr("paint-order", "stroke")
.attr("stroke", halo)
.attr("stroke-width", haloWidth)
.attr("font-size", (d) => Math.min(fontSizeRange[1], fontScale(rScale(d.value))))
.attr("dy", "0.32em");
if (label != null) text.append("tspan").text((d) => label(d.data, d));
text
.append("tspan")
.attr("fill-opacity", 0.7)
.attr("dx", label == null ? null : 3)
.text((d) => format(d.value));
if (title != null) cell.append("title").text((d) => title(d.data, d));
return svg.node();
}