{
const root = tree(formatted_table)
const height = width
root.x0 = height / 2
root.y0 = 0
root.descendants().forEach((n) => {
if (n.children) {
if (n.depth >= 3) {
n._children = n.children
n.children = null
}
}
})
const svg = d3.select(DOM.svg(width, height))
svg
.style("width", width)
.style("height", height)
const updateNodes = (source) => {
const height = width
const scale = d3.scaleLinear().domain([0, 12]).range([0.2, 1.0])
const color = (d) => d3.interpolateRainbow(scale(d))
const duration = 600
const nodes = root.descendants()
const links = root.links()
const size = 0
let maxTextLength = 0
const node = svg.selectAll("g.node")
.data(nodes, (d) => d.data.data.id)
const nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", (d) => `
rotate(${source.x0 * 180 / Math.PI - 90})
translate(${source.y0},0)`)
.attr("fill", "none")
nodeEnter.selectAll(".textLabels").remove()
const offset = 2
nodeEnter.append("text")
.attr("dy", ".2em")
.attr("class", "textLabels")
.attr("font-size", "8px")
.attr("text-anchor", function (d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function (d) {
const distance = d.children ? 2.5 : 5 + offset
return d.x < 180 ? `translate(${distance})` : `rotate(180)translate(-${distance})`
})
.text((d) => d.data.data.id)
.each(function (d, i, n) {
if (d._children) {
const distance = d.children ? 2.5 : 5 + offset
const thisWidth = n[i].getComputedTextLength()
maxTextLength = Math.max(maxTextLength, d.y + thisWidth + distance)
}
})
const updateSvg = () => {
// THIS IS WHERE THE RESIZE MAGIC HAPPENS
const newSize = maxTextLength * 2
svg
.transition()
.duration(1500)
.style("width", width)
.style("height", height)
.style("padding", "10px")
.style("box-sizing", "border-box")
.style("font", "8px sans-serif")
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", `-${newSize / 2} -${newSize / 2} ${newSize} ${newSize}`)
}
updateSvg()
// ****************** nodes section ***************************
nodeEnter.append("circle")
.attr("class", "circleNode")
.attr("r", 1e-6)
.attr("title", (d) => d.depth)
.attr("cursor", "pointer")
.on("click", (d) => {
if (d.depth > 2) {
d.children = d.children ? null : d._children;
updateNodes(d)
}
})
const nodeUpdate = nodeEnter.merge(node)
nodeUpdate.transition()
.delay(800)
.duration(duration)
.attr("fill", (d) => d._children ? "white" : color(d.depth))
.attr("stroke", (d) => d._children ? color(d.depth) : "none")
.attr("stroke-width", (d) => d._children ? 1 : 0)
.attr("transform", (d) => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
`)
.attr("cursor", "pointer")
nodeUpdate.selectAll(".circleNode")
.attr("r", (d) => d.children ? 2.5 : 5)
.attr("cursor", (d) => d.children ? "pointer" : "auto")
// Update the node attributes and style
nodeUpdate.selectAll(".textLabels")
.attr("stroke", "white")
.attr("stroke-width", 0)
.attr("fill", (d) => d._children ? color(d.data.level) : 0)
.style("fill-opacity", (d) => d._children ? 1 : 0)
.each(function (d, i, n) {
if (d._children) {
const distance = d.children ? 2.5 : 5 + offset
const thisWidth = n[i].getComputedTextLength()
maxTextLength = Math.max(maxTextLength, d.y + thisWidth + distance)
}
})
.call(updateSvg)
// Remove any exiting nodes
const nodeExit = node.exit().transition()
.duration(duration / 2)
.attr("d", d3.linkRadial()
.angle((d) => source.x0)
.radius((d) => source.y0))
.remove()
// On exit reduce the node circles size to 0
nodeExit.select("circle")
.attr("r", 1e-6)
.attr("fill", "none")
// On exit reduce the opacity of text labels
nodeExit.selectAll(".textLabels")
.attr("fill", "none")
// ****************** links section ***************************
// Update the links...
const link = svg.selectAll("path.link")
.data(links, function (d) {
return d.target.data.data.id;
})
// Enter any new links at the parent"s previous position.
const linkEnter = link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", d3.linkRadial()
.angle((d) => source.x0)
.radius((d) => source.y0))
.attr("fill", "none")
.attr("stroke-width", 1)
.attr("stroke", "grey")
// UPDATE
const linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.delay(800)
.attr("d", d3.linkRadial()
.angle((d) => d.x)
.radius((d) => d.y))
// Remove any exiting links
link.exit().transition()
.duration(duration)
.attr("d", d3.linkRadial()
.angle((d) => source.x0)
.radius((d) => source.y0))
.remove();
// Store the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
})
}
updateNodes(root)
return svg.node()
}