Public
Edited
Jan 25, 2023
11 forks
Importers
45 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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()
}
Insert cell
s=d3.scaleOrdinal().range(['A', 'B', 'C'])
Insert cell
s('g')
Insert cell
function getPaths(tree, { bundleSize=1}={}) {
return tree.leaves().map((leaf, i, a) => leaf.path(tree).reverse()//.slice(1)
// .filter(d => d.height > 1)
.flatMap(d => {
const leaves = (d || d.parent || d).leaves()
const bundled = Math.pow(2, bundleSize - 1)
const index = Math.floor(leaves.indexOf(leaf) / bundled) * bundled
const frac = (index + .5) / leaves.length
return Object.assign(
d.interpolators
.map(interpolate => interpolate(frac).concat())
.slice(0, d.height + 1)// slice because at the tips we don't want midpoints
.concat(d.height <= 1 ? [d.interpolators[d.interpolators.length-1](frac).concat()] : []),
{}
)
})
)
}
Insert cell
function lineLineIntersection(A,B,C,D){
// Line AB represented as a1x + b1y = c1
const a1 = B[1] - A[1]
const b1 = A[0] - B[0]
const c1 = a1*(A[0]) + b1*(A[1])

// Line CD represented as a2x + b2y = c2
const a2 = D[1] - C[1]
const b2 = C[0] - D[0]
const c2 = a2*(C[0])+ b2*(C[1])

const determinant = a1*b2 - a2*b1

if (determinant == 0) { return null }
else {
return [
(b2*c1 - b1*c2)/determinant,
(a1*c2 - a2*c1)/determinant,
]
}
}
Insert cell
Insert cell
thumbSz = [2*200,2*200]
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