function renderHierarchy(container, root, {
viewSize=[100, 100], bundleSize,
circles, thicknesses, branching,
parallelPaths, smoothParallelPaths, pathColor,
}={}) {
let layers
if (Array.isArray(container)) {
layers = container
container = layers[0]
}
const bounds = d3.zip(
d3.extent(root.descendants(), d => d.x),
d3.extent(root.descendants(), d => d.y)
)
const scale = Math.min(
viewSize[0] / (bounds[1][0] - bounds[0][0]),
viewSize[1] / (bounds[1][1] - bounds[0][1])
) * .95
const sel = container ? d3.select(container) : d3.create('g')
const g = sel.selectAll('g.nodes')
.data([null])
.join('g').attr('class', 'nodes')
.attr('transform', `translate(${
viewSize[0] / 2 -
scale * (bounds[0][0] + bounds[1][0]) / 2
} ${
viewSize[1] / 2 +
scale * (bounds[0][1] + bounds[1][1]) / 2
}) scale(${scale} ${-scale})`)
g.selectAll('circle.node')
.data(!circles ? [] : root.descendants())
.join('circle').attr('class', 'node')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 2/scale)
g.selectAll('line.link')
.data(branching ? root.links() : [])
.join('line').attr('class', 'link')
.attr('x1', l => l.source.x)
.attr('y1', l => l.source.y)
.attr('x2', l => l.target.x)
.attr('y2', l => l.target.y)
.attr('stroke', '#333')
.attr('stroke-width', 1/scale)
g.selectAll('line.thickness')
.data(!thicknesses ? [] : root.links())
.join('line').attr('class', 'thickness')
.attr('x1', l => (l.target.sign === -1 ? l.source.bL : l.source.bR)[0])
.attr('y1', l => (l.target.sign === -1 ? l.source.bL : l.source.bR)[1])
.attr('x2', l => (l.target.sign === -1 ? l.target.bL : l.target.bR)[0])
.attr('y2', l => (l.target.sign === -1 ? l.target.bL : l.target.bR)[1])
.attr('stroke', '#000')
.attr('stroke-width', 1/scale)
.attr('stroke-dasharray', `${4/scale} ${3/scale}`)
g.selectAll('line.thickness2')
.data(!thicknesses ? [] : root.descendants().flatMap(d => d.interpolators.map(interpolate => [
interpolate(0).concat(), interpolate(1).concat()
])))
.join('line').attr('class', 'thickness2')
.attr('x1', l => l[0][0])
.attr('y1', l => l[0][1])
.attr('x2', l => l[1][0])
.attr('y2', l => l[1][1])
.attr('stroke', '#00c')
.attr('stroke-width', 1/scale)
//.attr('stroke-dasharray', `${4/scale} ${3/scale}`)
const color = pathColor || (
(d, i, a) => '#33d' && (d3.interpolateTurbo || d3.interpolateRdBu)(i/a.length)
)
const ordinal = d3.scaleOrdinal().range(layers ? layers.map((l,i) => i) : [0])
;(layers ? d3.selectAll(layers) : sel)
.selectAll('path.parallel')
.data(!parallelPaths ? [] : (_, k) => getPaths(root, {bundleSize}).map((path, i, a) => {
path.color = color(path, i, a)
return path
}).filter(path => ordinal(path.color) === k))
.join('path').attr('class', 'parallel')
.attr('stroke', path => path.color).attr('fill', 'none')
.attr('stroke-width', 1*.25 / scale)
//.style('opacity', .5)
.attr('d', d3.line().curve(smoothParallelPaths ? d3.curveBasis : d3.curveLinear))
.attr('transform', `translate(${
viewSize[0] / 2 -
scale * (bounds[0][0] + bounds[1][0]) / 2
} ${
viewSize[1] / 2 +
scale * (bounds[0][1] + bounds[1][1]) / 2
}) scale(${scale} ${-scale})`)
return sel.node()
}