Published
Edited
Jun 13, 2020
Importers
2 stars
Insert cell
Insert cell
Insert cell
renderTree = (root, options = {}) => {
const {
toLabel = d => d.id,
labelSize = 11,
labelFont = "sans-serif",
offsetBreadth = 25, // horizontal offset between nodes.
offsetDepth = 45, // vertical offset between nodes.
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()
}
Insert cell
layoutTree = (root, options = {}) => {
const { offsetBreadth, offsetDepth } = options
root.dx = offsetBreadth
root.dy = offsetDepth
const tree = d3.tree().nodeSize([root.dx, root.dy])(root)

const rootArray = []
root.eachBefore(d => rootArray.push(d))
rootArray.sort((a, b) => (+a.id) - (+b.id))

tree.eachBefore(d => {
const index = rootArray.findIndex(it => it.data === d.data)
d.y = (index + 1) * offsetBreadth
d.x = d.depth * offsetDepth
})
//tree.eachBefore(d => {
// const { x, y } = d
// d.x = y
// d.y = x + 200
//})
//let index = 0
//tree.eachBefore(d => {
// d.x = d.depth * offsetDepth
// d.y = index * offsetBreadth
// index += 1
//})

let x0 = Infinity
let x1 = -Infinity
tree.each(d => {
if (d.x > x1) x1 = d.x
if (d.x < x0) x0 = d.x
})

let y0 = Infinity
let y1 = -Infinity
tree.each(d => {
if (d.y > y1) y1 = d.y
if (d.y < y0) y0 = d.y
})

return { tree, x0, x1, y0, y1 }
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more