renderTree = (root, options = {}) => {
const {
toLabel = d => d.id,
labelSize = 11,
labelFont = "sans-serif",
offsetBreadth = 25,
offsetDepth = 45,
prefHeight = 0,
prefWidth = 600,
} = options
const { tree, x0, x1, y0, y1 } = layoutTree(root, { offsetBreadth, offsetDepth })
const innerWidth = y1 - y0
const margins = Math.max(0, (Math.min(width, prefWidth) - innerWidth) / 2)
const containerTranslate = { y: -y0 + margins, x: tree.dx - x0 }
const container = ({ children, ...otherProps }) => ({
append: "g",
fontFamily: labelFont,
fontSize: labelSize,
children: children.flat(),
transform: `translate(${containerTranslate.y}, ${containerTranslate.x})`,
...otherProps,
})
const nodeSize = 5
const nodeLabelOffset = 12
const nodes = ({ children, ...otherProps }) => tree.descendants().map(d => ({
append: "g",
transform: `translate(${d.y}, ${d.x})`,
children: children(d),
...otherProps,
}))
const node = d => ({
append: "circle",
fill: d.children ? "#999" : "#fff",
stroke: d.children ? "#999" : "#999",
strokeLinejoin: "round",
strokeWidth: 2,
r: nodeSize,
})
const nodeLabel = d => ({
append: "text",
text: toLabel(d),
dy: "0.31em",
y: d.children ? nodeLabelOffset : nodeLabelOffset,
textAnchor: d.children ? "middle" : "middle",
fill: "black",
})
const links = ({ children, ...otherProps }) => tree.links().map(d => ({
append: "g",
transform: `translate(${d.y}, ${d.x})`,
children: children(d),
...otherProps,
}))
const linkShape = d => d3.linkVertical()({
source: [d.source.y, d.source.x],
target: [d.target.y, d.target.x] ,
})
const link = d => ({
append: "g",
children: [{
append: "path",
d: linkShape(d),
fill: "none",
stroke: "#ccc",
strokeOpacity: 1,
strokeWidth: 1.5,
}],
})
const data = [
container({
children: [
links({
children: d => [
link(d),
]
}),
nodes({
children: d => [
node(d),
nodeLabel(d),
]
}),
]
})
]
const svg = d3.select(DOM.svg(width, x1 - x0 + tree.dx * 2))
d3.render(svg, data)
return svg.node()
}