nestedTree = (origRoot, showOwnerLinks = false) => {
const root = prepareRoot(origRoot);
const nodes = root.descendants();
const links = root.links();
const height = (nodes.length + 1) * nodeSize;
const svg = d3
.create("svg")
.attr("width", width / 2)
.attr("height", height)
.attr("viewBox", [-nodeSize / 2, (-nodeSize * 3) / 2, width / 2, height])
.attr(
"style",
"max-width: 100%; height: auto; font: 10px sans-serif; overflow: visible;"
);
const g = svg
.append("g")
.attr("transform", "translate(220,0)")
.attr("name", "move-to-middle");
const linksGroup = g
.append("g")
.attr("fill", "none")
.attr("stroke", "#333")
.attr("name", "links");
const nodesGroup = g.append("g").attr("name", "nodes");
const ownerGroup = g.append("g").attr("name", "owner-arcs");
draw(nodes, links);
function draw(nodes, links) {
const entry = g.transition().duration(700);
const update = g.transition().duration(700);
const exit = g.transition().duration(400);
const link = linksGroup
.selectAll("path")
.data(links, (l) => `${l.source.data.id}-${l.target.data.id}`)
.join(
(enter) =>
enter
.append("path")
.attr("d", linkPath)
.attr("stroke-dashoffset", strokeDashOffset)
.attr("stroke-dasharray", strokeDashArray)
.transition(entry)
.attr("stroke-dashoffset", 0),
(update) => {
return update.transition(update).attr("d", linkPath);
},
(exit) =>
exit
.attr("stroke-dashoffset", 0)
.transition(exit)
.attr("opacity", 0)
// animate pathlength to 0
.attr("stroke-dashoffset", strokeDashOffset)
.attr("stroke-dasharray", strokeDashArray)
.remove()
);
const node = nodesGroup
.selectAll("g")
.data(nodes, (d) => d.data.id)
.join(
(enter) => drawNode(enter),
(update) => {
update
.selectAll("circle")
.data((d) => d)
.transition(update)
.attr("cx", (d) => d.depth * nodeSize);
update
.selectAll("text")
.data((d) => d)
.transition(update)
.attr("x", (d) => d.depth * nodeSize + 6);
update.selectAll("circle").attr("cx", (d) => d.depth * nodeSize);
return update
.transition(update)
.attr("transform", (d) => `translate(0,${d.index * nodeSize})`);
},
(exit) => exit.transition().attr("opacity", 0).remove()
);
if (showOwnerLinks && !showOnlyDOM) {
const callees = nodes.filter((d) => {
// only show owner links between components, not host elements or fragments
return !d.data.isDOM && d.data.owner && d.data.name !== "[ ]";
});
const ownerArcs = ownerGroup
.selectAll("g")
.data(callees)
.join("g")
.attr("text-align", "middle");
ownerArcs
.append("path")
.attr("stroke", "black")
.attr("stroke-dasharray", 2)
.attr("fill", "none")
.attr("d", (d, i) => {
// Arc from callerNode to current node (d)
const { x1, y1, x2, y2, r } = d.data;
return arc({ x1, y1, x2, y2, r }); // ideally wouldn't have arc but arc-y curve, flatter curve with larger height
});
// TODO show only on hover or click
// callerArcs
// .append("text")
// .attr("transform", (d) => {
// const { x1, x2, y1, y2, r, id, owner } = d.data;
// // mutable debug = { x1, x2, y1, y2, id, owner };
// // NOT GOOD, WE NEED FIND POSITION ALONG ARC, EG 0.5 OF ARC
// return `translate(${x1 - r}, ${y1 + r})`;
// })
// .attr("stroke", "white")
// .attr("paint-order", "stroke")
// .text((d) => `${d.data.owner} calls ${d.data.id || d.data.name}`);
}
// function reset() {
// link.attr("stroke", "black");
// nodeCircles.attr("fill", nodeCircleFill);
// }
// function onClick(e, d) {
// reset();
// const highlight = new Set(d.ancestors());
// nodeCircles
// .filter((circle) => highlight.has(circle))
// .attr("fill", "hotpink");
// link
// .filter((link) => highlight.has(link.target))
// .attr("stroke", "hotpink")
// .raise();
// }
}
return Object.assign(svg.node(), { draw });
}