Published
Edited
Mar 7, 2021
2 forks
7 stars
Insert cell
Insert cell
chart = {
const root = tree(d3.hierarchy(data)
.sort((a, b) => d3.ascending(a.data.name, b.data.name)));
const svg = d3.create("svg");

const content = svg.append('g').attr('class', 'content');
const zoom = d3
.zoom()
.scaleExtent([1, 8])
.on('zoom', (e) => {
content.attr('transform', e?.transform)
});
content.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-width", 2)
.selectAll("path")
.data(tree(root).links())
.enter().append("line")
.attr("class", "link")
.attr("stroke","#ccc")
.attr("x1", d => radialPoint(d.source.x,d.source.y)[0])
.attr("y1", d => radialPoint(d.source.x,d.source.y)[1])
.attr("x2", d => radialPoint(d.target.x,d.target.y)[0])
.attr("y2", d => radialPoint(d.target.x,d.target.y)[1]);
content.append("circle")
.attr("r", width / 6)
.attr("fill", "gray")
.attr("fill-opacity", 0.2);
content.append("circle")
.attr("r", width / 3)
.attr("fill", "gray")
.attr("fill-opacity", 0.2);
const nodeEnter = content.append("g")
nodeEnter.selectAll("g")
.data(root.descendants())
.join("g")
.attr("transform", d => {
const xPos = radialPoint(d.x,d.y)[0];
const yPos = radialPoint(d.x,d.y)[1];
return `translate(${-xPos}, ${-yPos})`});
nodeEnter.selectAll("g")
.append("circle")
.attr("class", "border")
.attr("r", d => getRadius(d) + 3)
.attr("fill-opacity", 0.5)
.attr("fill", d => d.children ? "green" : "blue")
let currentZoom = 0;
const nodes = nodeEnter.selectAll("g")
.attr("class", "nodes")
.append("circle")
.attr("class", "photo")
.attr("fill",(d, i) => `url(#avatar_${i})`)
.attr("transform", d =>{
return `
translate(${-getRadius(d)}, ${-getRadius(d)})
`})
.attr("cx", getRadius)
.attr("cy", getRadius)
.attr("r", getRadius)
.on('click', (e, d) => {

const xPos = radialPoint(d.x,d.y)[0];
const yPos = radialPoint(d.x,d.y)[1];
const t = d3.transition().duration(750);
const selected = new Set([...d.descendants(), ...d.ancestors()])
const newZoom = d.depth === 0 ? 1 : d.depth;
d3.selectAll(".nodes .border")
.transition()
.duration(300)
.attr("r", d => getRadius(d) + 3)
d3.selectAll(".nodes .photo")
.transition()
.duration(300)
.attr("r", getRadius)
if(d.depth){
d3.selectAll(".nodes .border").filter(dd => filterActiveNodes(dd, selected))
.transition()
.duration(300)
.attr("r", d => getActiveRadius(d) + 3)
d3.selectAll(".nodes .photo").filter(dd => filterActiveNodes(dd, selected))
.transition()
.duration(300)
.attr("r", getActiveRadius)
}

if(currentZoom <= newZoom){
content
.transition(t).call(zoom.translateTo, -xPos, -yPos)
.transition(t).call(zoom.scaleTo, newZoom);
}else{
content
.transition(t).call(zoom.scaleTo, newZoom)
.transition(t).call(zoom.translateTo, -xPos, -yPos);
}
currentZoom = newZoom;

});

nodeEnter.selectAll("g")
.append("svg:defs").append("svg:pattern")
.attr("id", (d, i) => `avatar_${i}`)
.attr("width", getSize)
.attr("height", getSize)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", d => d.data.url)
.attr("width", getSize)
.attr("height", getSize)
.attr("preserveAspectRatio", "xMidYMid slice")
.attr("x", 0)
.attr("y", 0);

const res = svg
.attr('width', width + 100)
.attr('height', width + 100)
.node();

content
.call(zoom.translateTo, 0, 0);

return res;
}


Insert cell
data = FileAttachment("data@1.json").json()
Insert cell
width = 800
Insert cell
radius = width/2
Insert cell
avatarSize = 60
Insert cell
avatarRadius = avatarSize / 2
Insert cell
childAvatarSize = avatarSize * 0.5
Insert cell
childAvatarRadius = childAvatarSize / 2
Insert cell
getSize = d => d.depth===0 ? avatarSize : avatarSize / d.depth
Insert cell
getRadius = d => d.depth===0 ? avatarRadius : avatarRadius / d.depth
Insert cell
getActiveRadius = d => getRadius(d) * 1.4
Insert cell
radialPoint = (x, y) => [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)]
Insert cell
tree = d3.cluster().size([2 * Math.PI, radius])
Insert cell
d3 = require("d3@6")
Insert cell
filterActiveNodes = (d, selection) => selection.has(d) && d.depth > 1;
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