D3 Treemap and Pack Layout Integration
This example loads a static relation graph in `fixed` mode, rebuilds a D3 hierarchy from the live graph tree, and writes treemap or circle-pack geometry back into relation-graph nodes. A floating selector lets users switch the same graph between the two D3 layouts while keeping custom node slots, a minimap, shared canvas settings, and image export.
Switch Between D3 Treemap and Circle-Pack Layouts in relation-graph
What This Example Builds
This example builds a full-height hierarchy playground where one loaded relation graph can be reflowed by two different D3 hierarchy algorithms. The canvas shows a generic tree of nodes, a floating description panel exposes the layout selector, and a built-in mini-view stays visible as an overview of the current arrangement.
The key visual result is that the same graph can switch between treemap-style rectangles and circle-pack bubbles without replacing the relation-graph scene itself. Custom node rendering, line labels, the minimap, and shared canvas utilities remain active while only the layout calculation changes.
How the Data Is Organized
The example starts from a static inline RGJsonData object inside initializeGraph(). It declares one rootId, a flat nodes array, and a flat lines array, then backfills missing line ids with L${index} before the data is loaded into the graph.
That inline JSON is only the first stage of the data flow. After setJsonData(...) runs, the example asks the live graph instance for the root node and rebuilds a D3-friendly hierarchy from rootNode.lot.childs. Leaf nodes receive value: 1, internal nodes receive value: 0, and that derived tree becomes the input for either treemap(...) or pack().
In a real application, the same pattern could start from product categories, file-system folders, organization layers, dependency groups, or any other hierarchy where you want relation-graph rendering features but prefer an external layout engine for geometry.
How relation-graph Is Used
RGProvider wraps the page so RGHooks.useGraphInstance() can resolve the active graph instance. The graph itself is configured with layout.layoutName = 'fixed', which is the critical setup for this demo: relation-graph renders and manages the scene, but the final coordinates and node sizes are authored by the D3 pass instead of by a built-in layout preset.
The component loads the inline graph with setJsonData(...), then immediately calls a custom doMyLayout() helper. That helper retrieves the live root node with getNodeById('root'), passes it to applyD3TreemapOrPackLayout(...), and then recenters and fits the result with moveToCenter() and zoomToFit().
The D3 integration happens in MyGraphLayout4D3Treemap.ts. It rebuilds hierarchy input from live relation-graph nodes, runs either treemap(...).tile(treemapSquarify) or pack() against the current relation-graph view size, sorts the computed nodes by size, and writes width, height, position, z-index, and node-shape changes back through updateNode(...). This is the main teaching point of the example: relation-graph remains the runtime graph surface while D3 becomes an interchangeable geometry engine.
Slots are preserved throughout that process. RGSlotOnNode renders custom node bodies, gives the root node a stronger visual treatment, and mirrors the current layout mode with rounded rectangles versus fully rounded circles. RGSlotOnView mounts RGMiniView, so the overview map stays available even though the node geometry is coming from D3. Styling is further refined in my-relation-graph.scss, which turns line labels into compact white badges.
The floating shell comes from shared helper components rather than from example-local graph code. DraggableWindow provides the explanatory panel, drag and minimize behavior, a settings overlay, and image export. SimpleUISelect is used for the two-option layout switcher.
Key Interactions
- The layout selector switches
layoutShapebetweenrectandcircle, and each change reruns the external layout on the already loaded graph instance. - On first mount, the example loads data and immediately performs the first D3 layout pass, so users never see an unarranged fixed-layout scene.
- Clicking blank canvas space clears checked graph state through
graphInstance.clearChecked(). - The floating helper window can be dragged, minimized, and expanded into a settings overlay.
- The settings overlay changes canvas wheel mode and drag mode through
setOptions(...), and it can export the current graph as an image. RGMiniViewprovides overview navigation for the relaid-out scene.
Key Code Fragments
This fragment shows that the example intentionally keeps relation-graph in fixed mode so D3 can supply the final geometry.
const graphOptions: RGOptions = {
debug: false,
layout: {
layoutName: 'fixed'
},
defaultNodeShape: RGNodeShape.rect,
defaultLineShape: RGLineShape.StandardCurve
};
This fragment shows the two-stage loading flow: load graph data first, then run the custom layout against the live graph instance.
myJsonData.lines.forEach((line, index) => {
if (!line.id) line.id = `L${index}`;
});
await graphInstance.setJsonData(myJsonData);
await doMyLayout();
This fragment proves that the runtime switch is driven by component state rather than by rebuilding a different graph dataset.
useEffect(() => {
doMyLayout();
}, [layoutShape]);
<SimpleUISelect
data={[
{ value: 'rect', text: 'Rectangle Treemap' },
{ value: 'circle', text: 'Circle Pack' }
]}
This fragment is the core D3 handoff: the helper rebuilds a hierarchy from live nodes and picks either treemap or pack based on the selected mode.
const treemapInput = buildTreemapDataNode(rootNode);
let layout;
if (shape === 'rect') {
layout = treemap<TreemapDataNode>()
.size([graphInstance.options.viewSize.width, graphInstance.options.viewSize.height])
.padding(padding)
.tile(treemapSquarify);
} else {
layout = pack()
.size([graphInstance.options.viewSize.width, graphInstance.options.viewSize.height]);
}
This fragment shows how the computed D3 geometry is written back into live relation-graph nodes.
if (rgNode) {
const nodeProps: Partial<RGNode> = {};
if (shape === 'rect') {
nodeProps.nodeShape = 1;
nodeProps.x = n.x - offsetX;
nodeProps.y = n.y - offsetY;
} else {
nodeProps.nodeShape = 0;
nodeProps.x = n.x - offsetX - n.width / 2;
nodeProps.y = n.y - offsetY - n.height / 2;
}
nodeProps.width = n.width;
nodeProps.height = n.height;
graphInstance.updateNode(n.id, nodeProps);
}
This fragment shows that the shared utility shell also exposes image export on top of the layout demo.
const canvasDom = await graphInstance.prepareForImageGeneration();
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
if (imageBlob) {
downloadBlob(imageBlob, 'my-image-name');
}
await graphInstance.restoreAfterImageGeneration();
What Makes This Example Distinct
The prepared comparison data places this example near use-dagre-layout, use-dagre-layout-2, and layout-tree, but its emphasis is different. Compared with the Dagre examples, it is more about hierarchy-aware geometry remapping than directed-graph placement. It rebuilds a D3 hierarchy from rootNode.lot.childs and writes back node width, height, z-index, and shape, instead of mainly repositioning already measured nodes.
Compared with use-dagre-layout-2, the distinctive point is not parameter tuning for one algorithm. It is runtime switching between two external hierarchy layout families on the same live graph instance. Compared with layout-tree, the distinctive point is not built-in tree orientation changes. It is showing that a third-party layout engine can drive relation-graph while preserving slots, minimap support, line-label styling, and the shared viewer shell.
The comparison and rarity records support a conservative summary: this is a strong starting point when a project needs fixed-mode bootstrapping, live-tree-to-D3 hierarchy rebuild, runtime treemap-versus-pack switching, and geometry writeback in one compact viewer. It should not be described as the only external-layout demo or as a general editor example.
Where Else This Pattern Applies
This pattern transfers well to applications that already have hierarchical data but want more than relation-graph’s built-in layouts for node geometry. Examples include category explorers, storage or folder overviews, portfolio breakdowns, capability maps, and organization summaries where the same hierarchy may need both packed and boxed presentations.
It is also useful as an integration template. Teams can keep relation-graph for node slots, overlays, canvas behavior, and navigation, while swapping in another hierarchy engine that computes coordinates and sizes externally before writing them back into the live graph.