Published
Edited
Jul 21, 2020
2 forks
11 stars
Insert cell
Insert cell
chart(width, 600)
Insert cell
Insert cell
chart(width, 100)
Insert cell
gratzl = {
function depthSort(a, b) {
if (a.maxDescendantDepth > b.maxDescendantDepth) {
return -1;
} else if (a.maxDescendantDepth < b.maxDescendantDepth) {
return 1;
}
return 0;
}

return function() {
var dx = 5,
dy = 50,
widths = [];

function setTreeX(node, val) {
node.x = val;
widths[node.depth] = val;
if (node.children) {
node
.leaves()
.sort(depthSort)
.forEach(function(leaf) {
if (typeof leaf.x === 'undefined') {
var width = Math.max.apply(
null,
widths.slice(node.depth, leaf.depth + 1)
);
setTreeX(leaf, val > width ? val : width + 1);
}
});
}

if (node.parent && typeof node.parent.x === 'undefined') {
setTreeX(node.parent, val);
}
}

function tree(root, activeNode) {
// Added by @fil 2020-07-21
// reset all values
widths.length = 0;
root.each(d => {
delete d.x;
delete d.y;
});

/*
* set maxDescendantDepth on each node,
* which is the depth of its deepest child
*
* */
root.leaves().forEach(function(leaf) {
leaf.ancestors().forEach(function(leafAncestor) {
if (
!leafAncestor.maxDescendantDepth ||
leaf.depth > leafAncestor.maxDescendantDepth
) {
leafAncestor.maxDescendantDepth = leaf.depth;
}
});
});

/* rendering should start at the deepest leaf of activeNode. */
var deepestLeaf = activeNode;
activeNode.leaves().forEach(function(leaf) {
if (deepestLeaf.depth < leaf.depth) {
deepestLeaf = leaf;
}
});

setTreeX(deepestLeaf, 0);

var maxX = Math.max.apply(null, widths);
var maxY = Math.max.apply(
null,
root.leaves().map(function(leaf) {
return leaf.depth;
})
);
root.each(function(node) {
sizeNode(node, maxX, maxY);
});
return root;
}

function sizeNode(node, maxX, maxY) {
node.x = dx - (dx / maxX) * node.x;
node.y = (dy / maxY) * node.depth;
}

tree.size = function(x) {
return arguments.length ? ((dx = +x[0]), (dy = +x[1]), tree) : [dx, dy];
};

return tree;
};
}
Insert cell
function chart(width, height) {
const margin = { left: 10, top: 10, bottom: 10, right: 100 };

const root = d3.hierarchy(data);

const tree = gratzl().size([
width - margin.left - margin.right,
height - margin.top - margin.bottom
]);

const svg = d3
.create("svg")
.attr("viewBox", [-margin.left, -margin.top, width, height]);
svg
.append("defs")
.append("style")
.text(
`
text { visibility: hidden }
.text-visible text { visibility: visible }
.hover text { visibility: visible }
`
);

const g = svg
.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10);

const link = g
.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5);

const nodes = g
.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3);

const node = nodes
.selectAll("g")
.data(root.descendants())
.join("g")
.on("click", draw)
.on("pointerenter", function() {
d3.select(this)
.raise()
.classed("hover", true);
})
.on("pointerout", function() {
d3.select(this).classed("hover", false);
});

node
.append("circle")
.attr("fill", d => (d.children ? "#555" : "#999"))
.attr("r", 2.5);

node
.append("text")
.attr("dy", "0.31em")
.attr("x", d => 6)
.attr("text-anchor", "start")
.text(d => d.data.name)
.clone(true)
.lower()
.attr("stroke", "white");

function draw(active) {
tree(root, active || root);

node
.transition()
.duration(active ? 750 : 0)
.attr("transform", d => `translate(${d.x},${d.y})`);

node.classed(
"text-visible",
d => d.x > width - margin.left - margin.right - 1
);

link
.selectAll("path")
.data(root.links())
.join("path")
.transition()
.duration(active ? 750 : 0)
.attr(
"d",
d3
.linkVertical()
.x(d => d.x)
.y(d => d.y)
);
}

draw();

return svg.node();
}
Insert cell
data = FileAttachment("flare-2.json").json()
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