Horizontal D3 FlexTree with Composite Node Sizes
by Rajesh Danabal
HTML
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/[email protected]/build/d3-flextree.min.js"></script>
<svg width="1400" height="600"></svg>
CSS
svg {
font-family: sans-serif;
border: 1px solid #ccc;
}
rect {
fill: #4a90e2;
stroke: #333;
rx: 5;
ry: 5;
}
rect.leaf {
fill: #7ed6df;
}
text {
fill: white;
font-size: 12px;
pointer-events: none;
}
JavaScript
const data = {
name: "Root",
children: [
{
name: "Composite A",
children: [
{ name: "Leaf A1" },
{ name: "Leaf A2" },
{ name: "Leaf A3" }
]
},
{
name: "Composite B",
children: [
{
name: "Composite B1",
children: [
{ name: "Leaf B1.1" },
{ name: "Leaf B1.2" }
]
},
{ name: "Leaf B2" }
]
},
{ name: "Leaf C" }
]
};
// Constants for fixed leaf size and padding
const LEAF_WIDTH = 140;
const LEAF_HEIGHT = 50;
const H_PADDING = 30;
const V_PADDING = 20;
// Precompute size for nodes recursively
function computeNodeSize(node) {
if (!node.children || node.children.length === 0) {
// Leaf node: fixed size
node.width = LEAF_WIDTH;
node.height = LEAF_HEIGHT;
} else {
// Composite node: size based on children bounding box plus padding
node.children.forEach(computeNodeSize);
// width: max child width + horizontal padding * 2
node.width = Math.max(...node.children.map(c => c.width)) + H_PADDING * 2;
// height: sum of child heights + vertical padding * (child count - 1) + vertical padding * 2
node.height = node.children.reduce((sum, c) => sum + c.height, 0) +
V_PADDING * (node.children.length - 1) + V_PADDING * 2;
}
}
computeNodeSize(data);
// Use d3-flextree
const layout = d3.flextree()
.nodeSize(d => [d.data.height, d.data.width]) // Note: [height, width], but we'll swap later for horizontal
.spacing(() => 30);
const root = layout.hierarchy(data);
layout(root);
// Swap x and y to make it horizontal layout
root.each(d => {
const tmp = d.x;
d.x = d.y;
d.y = tmp;
});
const svg = d3.select("svg");
const g = svg.append("g").attr("transform", "translate(80, 50)");
...