Public
Edited
Feb 23, 2024
1 fork
2 stars
Insert cell
Insert cell
{
const svg = DOM.svg(width, height)
const sel = d3.select(svg)

const packLayout = d3.pack()
.size([width, height])
.radius(() => 40)
.padding(padding)

const rootNode = d3.hierarchy(data)

// run .sum() on the hierarchy; return 1 for size of leaf nodes
rootNode.sum(() => 1)

packLayout(rootNode)

renderDefs(rootNode.descendants())
renderCircles(rootNode.descendants())
renderLabels(rootNode.descendants())

function renderDefs (nodes) {
const defs = sel.append('defs')
.selectAll('pattern')
.data(nodes, d => d.data.id)
defs.join('pattern')
.attr('id', (d, i) => `p-${i}`)
.attr('width', 1)
.attr('height', 1)
.append('image')
.attr('xlink:href', d => {
const obj = d.data.data
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${obj.img}`
})
.attr('x', 5)
.attr('y', 0)
.attr('width', 70)
.attr('height', 70)
}

function renderCircles (nodes) {
const circles = sel
.selectAll('circle')
.data(nodes, d => d.data.id)
circles.join('circle')
.attr('class', d => {
if (d.depth === 0) {
return 'root'
} else if (d.depth === 1) {
return 'cluster'
} else {
return 'item'
}
})
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', d => d.r)
.attr('fill', (d, i) => {
const obj = d.data.data
const id = `p-${i}`
return `url(#${id})`
})
}

function renderLabels (nodes) {
const labels = sel
.selectAll('text')
.data(nodes, d => d.data.id)

labels.join('text')
.attr('x', d => d.x)
.attr('y', d => d.y)
.attr('dy', '40px')
.text(d => {
if (d.depth === 0 || d.depth === 1) {
return ''
} else {
return d.data.data.first_name
}
})
}
return svg
}
Insert cell
Insert cell
Insert cell
height = 1200
Insert cell
padding = 10
Insert cell
Insert cell
data = d3.stratify()
.id(d => d.name)
.parentId(d => d.parent)(flatData)
Insert cell
Insert cell
flatData = _.flattenDeep(teamData).map((d, i) => {
const obj = {...d} // use the spread operator to ensure I don't mutate `teamData`
// add the images in the same data for ease https://observablehq.com/@eesur/dicebear-avatar-generation-test
obj.img = `${i}.svg`
obj.first_name = names[i]
return obj
})
Insert cell
Insert cell
teamData = [
{name: 'root', parent: null}, // have a root node to start with for clusters
createTeam(20, 'A'),
createTeam(15, 'B'),
createTeam(10, 'C'),
createTeam(10, 'D'),
createTeam(8, 'E'),
createTeam(6, 'F'),
createTeam(3, 'G'),
createTeam(3, 'H'),
createTeam(2, 'I')
]
Insert cell
Insert cell
createTeam = (amount, team) => {
const members = d3.range(amount).map(d => {
return {
name: names[_.random(0, 328)],
parent: team
}
})
// return the root and it's children
return [ {name: team, parent: 'root'}].concat(members)
}
Insert cell
Insert cell
Insert cell
<hr>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=PT+Sans&display=swap" rel="stylesheet">

<style>
text {
font-family: 'PT Sans', sans-serif;
font-weight: 400;
font-size: 9px;
letter-spacing: 2px;
text-anchor: middle;
}
svg {
background: #f5f5b8
}
circle.item{
/* fill: #0f261f; */
cursor: none;
}
circle.cluster{
fill: #fefefe;
}
.root{
display: none;
}

</style>

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