Published
Edited
May 2, 2020
Fork of Random Tree
Insert cell
Insert cell
canvas = {
const svg = d3.select(DOM.svg(width, height)).attr("viewBox", [0, -20, width, height + 20]);
const layoutTree = d3.tree().separation(separation).size([width , height - 20]);
const renderLink = d3.linkVertical().x(d => d.x).y(d => d.y);
const Node = d3.hierarchy.prototype.constructor;
const root = new Node;
const nodes = [root];
const links = [];
const box = { height: 30, width: 40};
layoutTree(root);

let link = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#000")
// .attr("stroke", "orange")
.selectAll(".link");

let node = svg.append("g")
// .attr("stroke", "#fff")
.attr("stroke", "orange")
.attr("stroke-width", 2)
.selectAll(".node");

const interval = d3.interval(() => {
if (nodes.length >= 30) return interval.stop();

// Add a new node to a random parent.
const parent = nodes[Math.random() * nodes.length | 0];
const child = Object.assign(new Node, {parent, depth: parent.depth + 1});
if (parent.children) parent.children.push(child);
else parent.children = [child];
nodes.push(child);
links.push({source: parent, target: child});

// Recompute the layout.
layoutTree(root);

// Add entering nodes in the parent’s old position.
node = node.data(nodes);
// node = node.enter().append("circle")
// .attr("class", "node")
// .attr("r", 3)
// .attr("cx", d => d.parent ? d.parent.px : d.px = d.x)
// .attr("cy", d => d.parent ? d.parent.py : d.py = d.y)
// .merge(node);
node = node.enter().append("rect")
// .attr("class", "node")
.attr("x", d => d.x - responsiveWidth(d) / 2)
.attr("y", d => d.y - box.height / 2 )
// .attr("width", box.width )
.attr("width", responsiveWidth)
.attr("height", box.height)
.attr("fill", "white")
.attr("stroke", "black")
.attr("stroke-width", "1")
.merge(node);

// Add entering links in the parent’s old position.
link = link.data(links);
link = link.enter().insert("path", ".node")
.attr("class", "link")
.attr("d", d => {
const o = {x: d.source.px, y: d.source.py};
return elbow({source: o, target: o});
})
.merge(link);

// Transition nodes and links to their new positions.
const t = svg.transition()
.duration(duration);

link.transition(t)
.attr("d", elbow);

node.transition(t)
// .attr("cx", d => d.px = d.x)
// .attr("cy", d => d.py = d.y)
.attr("x", d => d.x - responsiveWidth(d) / 2)
.attr("y", d => d.y - box.height / 2)
.attr("width", responsiveWidth)
.attr("height", box.height)
.attr("fill", "white")
.attr("stroke", "black")
.attr("stroke-width", "1")
}, duration);

invalidation.then(() => interval.stop());

return svg.node();
}
Insert cell
function elbow(d, i) {
let source = d.source;
let target = d.target;
let half_y = (target.y + source.y)/2;
if(!source.x && !target.x) return;
if(source.x === target.x) return "M" + source.x + "," + source.y + "V" + target.y;
return "M" + source.x + "," + source.y
+ "V" + half_y + "H" + target.x
+ "V" + target.y;
// return "M" + d.source.x + "," + d.source.y
// + "V" + d.target.y + "H" + d.target.x
// + "v"+ (d.target.y - d.source.y) / 2;
}
Insert cell
function responsiveWidth(node) {
let defaultWidth = 100;
let parent = node.parent;
if(!parent) {
node.width = defaultWidth;
return defaultWidth;
}
let sibling = node.parent.children;
let len = sibling.length;
if(!sibling) {
node.width = node.parent.width - 1;
return node.width ;
}else {
// case 1
let start = sibling[0];
let end = sibling[len - 1];
let diff = end.x - start.x;
if(diff === 0){
node.width = node.parent.width - 1;
return node.width ;
}else {
let width = diff / (len - 1) - 2;
width = width > node.parent.width ? node.parent.width : width
node.width = width;
return node.width;
}
// case 2
// let index = sibling.findIndex((d) => d === node);
// if(index === len - 1) return defaultWidth;
// if(index === 0 && index < len - 1) {
// return (sibling[1].x - node.x) / 2 - 1;
// }else if(index > 0 && index == len - 1) {
// return (node.x - sibling[len - 2].x) / 2 - 1;
// }else {
// let forward = node.x - sibling[index - 1].x;
// let behind = sibling[index + 1].x - node.x;
// if(forward >= behind) {
// return behind / 2 - 2;
// }else{
// return forward / 2 - 2;
// }
// }
}
}
Insert cell
function separation(a, b) {
// return (a.parent == b.parent ? 1 : a.parent.children.length) / a.depth;
// return (a.parent == b.parent ? 1 : 2);
return (a.parent == b.parent ? 1 : 2) / a.depth;
}
Insert cell
height = 650
Insert cell
// width = 1000
Insert cell
duration = 750
Insert cell
d3 = require("d3@5")
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