Published
Edited
Jul 9, 2021
Fork of Tree graph
Importers
3 stars
Insert cell
md`# Tree graph`
Insert cell
Insert cell
radius= width*2/2.1
Insert cell
Insert cell
// radial tree and dendogram tree, pass in the tree parameter to generate different graphs
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
}
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
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
// serialize = {
// const xmlns = "http://www.w3.org/2000/xmlns/";
// const xlinkns = "http://www.w3.org/1999/xlink";
// const svgns = "http://www.w3.org/2000/svg";
// return function serialize(svg) {
// const reset = new Promise((resolve, reject) => {
// setTimeout(() => {
// svg.zoomReset()
// resolve('foo');
// }, 3000);
// })
// reset.then(res => {
// svg = svg.cloneNode(true);

// })

// };
// }
Insert cell
// viewof fontsize_i = Range([8, 35], {step: 1 ,value: 12, label: "Font Size"})
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