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;
}