chart = (data,tree, {w, h}={}) =>{
const fs= font_size + "px sans-serif"
let _width = w || width;
let _height = h || (_width *2)
let root=tree(d3.hierarchy(data).sort((a, b) => d3.ascending(a.data.name, b.data.name)));
let color = d3.scaleOrdinal().domain(tree(data).leaves().map(d => {return d.parent.data.name})).range(d3.schemeCategory10)
const svg = d3.select(DOM.svg(_width*1.5, _height*1.5))
.style("width", _width)
.style("height", _height)
.style("max-height", "100%")
.style("padding", "10px")
.style("box-sizing", "border-box")
.style("font", fs)
const g = svg.append("g");
const linkgroup = g.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5);
const nodegroup = g.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3);
const myScale = d3.scaleLinear()
.domain([d3.min(root.leaves().map(leaf => leaf.data.value)), d3.max(root.leaves().map(leaf => leaf.data.value))])
.range([0.3, 1]);
function newdata (animate = true) {
let root = tree(data);
let links_data = root.links();
let links = linkgroup
.selectAll("path")
.data(links_data, d => d.source.data.name+"_"+d.target.data.name);
links.exit().remove();
let newlinks = links
.enter()
.append("path")
.attr("d", d3.linkRadial()
.angle(d => d.x)
.radius(0.1));
let t = d3.transition()
.duration(animate ? 600 : 0)
.ease(d3.easeLinear)
.on("end", function() {
const box = g.node().getBBox();
svg.transition().duration(0).attr("viewBox", `${box.x} ${box.y} ${box.width} ${box.height}`);
});
let alllinks = linkgroup.selectAll("path")
alllinks
.transition(t)
.attr("d", d3.linkRadial()
.angle(d => d.x)
.radius(d => d.y));
let nodes_data = root.descendants().reverse();
let nodes = nodegroup
.selectAll("g")
.data(nodes_data, function (d) {
if (d.parent) {
return d.parent.data.name+d.data.name;
}
return d.data.name});
nodes.exit().remove();
let newnodes = nodes.enter().append("g");
let allnodes = animate ? nodegroup.selectAll("g").transition(t): nodegroup.selectAll("g");
allnodes
.attr("transform", d => `
rotate(${d.x * 180 / Math.PI - 90})
translate(${d.y},0)
`);
newnodes.append("circle")
.attr("r", 4.5)
// .on('mouseover', function(d, slicedData){
// if(d.children){
// d3.select(this).style("cursor", "pointer")
// }})
.on ("click", function (d) {
let altChildren = d.data.altChildren || [];
let children = d.data.children;
d.data.children = altChildren;
d.data.altChildren = children;
newdata ();
})
nodegroup.selectAll("g circle")
.attr("fill", d => { while (d.depth > 1) d = d.parent; return color(d.data.name); })
.attr("fill-opacity", d => myScale(d.data.value))
.attr("r", d => {
let altChildren = d.data.altChildren || [];
let children = d.data.children;
return d.children || (children && (children.length > 0 || altChildren.length > 0)) ? 5 : 3
})
.on('mouseover', function(d){
let altChildren = d.data.altChildren || [];
let children = d.data.children;
if(d.children || (children && (children.length > 0 || altChildren.length > 0))){
d3.select(this).style("cursor", "pointer")
}})
newnodes.append("text")
.attr("dy", "0.31em")
.text(d => d.data.name.replace(/\d*#/, ''))
.clone(true).lower()
.attr("stroke", "white")
;
nodegroup.selectAll("g text")
.attr("dy", "0.31em")
.attr("x", d => {
let altChildren = d.data.altChildren || [];
let children = d.data.children;
let check= d.children || (children && (children.length > 0 || altChildren.length > 0))
return d.x < Math.PI === !check ? 10 : -10})
.attr("text-anchor", d=>{
let altChildren = d.data.altChildren || [];
let children = d.data.children;
let check= d.children || (children && (children.length > 0 || altChildren.length > 0))
return d.x < Math.PI === !check ? "start" : "end"})
.attr("transform", d => d.x >= Math.PI ? "rotate(180)" : null)
.style("font", d => fs)
.style("font-weight", d => {let altChildren = d.data.altChildren || [];
let children = d.data.children;
return d.children || (children && (children.length > 0 || altChildren.length > 0)) ? "bold" : "normal"})
.attr("fill", "#161616")
.text(d => d.data.name.replace(/\d*#/, ''))
.filter(d => d.children)
.clone(true).lower()
.attr("stroke", "white")
.attr("stroke-width", 4);
}
newdata (false);
document.body.appendChild(svg.node());
const box = g.node().getBBox();
box.width = box.height = Math.max(box.width, box.height)*1.1;
svg.remove()
.attr("width", box.width)
.attr("height", box.height)
.attr("viewBox", `${box.x} ${box.y} ${box.width} ${box.height}`)
.attr("preserveAspectRatio","xMidYMid meet");
const zoom = d3.zoom()
.extent([[0, 0], [_width, _height]])
.scaleExtent([1, 8])
.on("zoom", zoomed);
svg.call(zoom);
function reset() {
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity,
d3.zoomTransform(svg.node()).invert([_width / 2, _height / 2])
);
}
function reset_imm() {
svg.call(
zoom.transform,
d3.zoomIdentity,
d3.zoomTransform(svg.node()).invert([_width / 2, _height / 2])
);
}
function clicked(event, [x, y]) {
event.stopPropagation();
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity.translate(_width / 2, _height / 2).scale(40).translate(-x, -y),
d3.mouse(svg.node())
);
}
function zoomed() {
g.attr("transform", d3.event.transform);
}
const originalNode = svg.node()
const newNode = Object.assign(originalNode, {
zoomIn: () => svg.transition().call(zoom.scaleBy, 1.5),
zoomOut: () => svg.transition().call(zoom.scaleBy, 0.8),
zoomReset: reset,
zoomReset_imm: reset_imm
})
return newNode
}