JavaScript is required

Folder Layout with Line Offset Control

This example shows how to render a nested dataset with relation-graph's built-in folder layout and a custom node slot that makes parent nodes look like folders. It also demonstrates a reusable runtime pattern: batch-updating loaded connectors so one slider changes the line start offset across the whole graph.

Folder Layout with Runtime Connector Offset Control

What This Example Builds

This example builds a left-to-right file-hierarchy viewer on top of relation-graph’s built-in folder layout. The rendered result looks like a compact folder tree: parent nodes are shown as folder-style rows, connectors use muted orthogonal routing, and expand holders appear on the right side of each branch.

Users can explore the hierarchy by expanding and collapsing branches, and they can adjust one routing parameter at runtime from a floating control window. The most instructive part of the example is that the slider does not rebuild the graph data; instead, it updates the already rendered lines so every connector leaves its source node with a different horizontal offset.

How the Data Is Organized

The source data starts as a nested tree in data.ts, where each item has an id, text, and optional children. Before anything is passed into relation-graph, the example converts that tree into explicit RGJsonData arrays with nodes and lines.

That preprocessing step matters because the example needs direct control over each connector. Once the data is flattened, every parent-child relationship becomes its own line record, which makes it possible to assign line ids, set explicit junction points, and later update the live connectors in one batch. In a real application, the same structure could represent a filesystem, a document outline, a category hierarchy, a menu tree, or any nested ownership structure that benefits from folder-style presentation.

How relation-graph Is Used

The graph instance is provided through RGProvider, and MyGraph uses RGHooks.useGraphInstance() to load data, center the viewport, fit the result, and update live lines after mount. The graph options focus on the folder layout: layoutName: 'folder', from: 'left', treeNodeGapH: 20, and treeNodeGapV: 10 define a compact horizontal hierarchy.

The visual defaults are tuned to reinforce the file-tree reading. Nodes are rectangular at 100x30, lines use RGLineShape.StandardOrthogonal, corners are rounded with defaultPolyLineRadius: 4, the default line color is #aaaaaa, and expand holders are placed on the right. The example also enables reLayoutWhenExpandedOrCollapsed, so branch expansion stays aligned with the folder layout instead of leaving stale spacing behind.

RGSlotOnNode is the main rendering customization. The slot checks node.rgChildrenSize and gives parent nodes a folder icon plus a light gray rounded background, while leaves only reserve empty icon space. That keeps the graph visually narrow and avoids turning the example into a card-style hierarchy demo.

The floating DraggableWindow is shared demo scaffolding, but it still shows practical relation-graph integration. Its settings panel reads current graph options from the graph store, updates wheelEventAction and dragEventAction through setOptions(...), and exports the current canvas through prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration().

Key Interactions

The primary interaction is the range slider in the floating window. It controls fromOffsetX, and a dependent effect iterates over the current lines with getLines() and reapplies fromJunctionPointOffsetX through updateLine(...). That makes the routing change visible immediately across the whole graph.

Branch exploration is the second important interaction. Because the graph uses the folder layout, places expand holders on the right, and enables relayout after expand or collapse, users can inspect deeper levels without losing the hierarchical shape.

The floating window itself is also interactive: it can be dragged, minimized, switched into a settings panel, and used to export the graph image. Those controls are inherited from shared demo utilities rather than unique to this example, but they do affect how the example is explored.

Key Code Fragments

This fragment shows that the example deliberately converts nested tree data into flat nodes and lines before loading the graph.

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [],
    lines: []
};

flattenTreeData(rootNodeJson, null, myJsonData.nodes, myJsonData.lines);
return myJsonData;

This recursive helper is the proof that every parent-child edge becomes an explicit line record.

treeNodes.forEach((nodeData) => {
    const node: JsonNode = {
        id: nodeData.id,
        text: nodeData.text
    };
    nodes.push(node);
    if (parent) {
        const line: JsonLine = {
            from: parent.id,
            to: nodeData.id
        };
        lines.push(line);
    }

This options block shows the example is built around relation-graph’s folder layout rather than a generic tree preset.

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'folder',
        from: 'left',
        treeNodeGapH: 20,
        treeNodeGapV: 10
    },
    defaultNodeShape: RGNodeShape.rect,
    defaultNodeWidth: 100,
    defaultNodeHeight: 30,

This initialization step proves that line routing is normalized before setJsonData(...) is called.

myJsonData.lines.forEach((line, index) => {
    if (!line.id) {
        line.id = `l${index + 1}`;
    }
    line.fromJunctionPoint = RGJunctionPoint.bottom;
    line.fromJunctionPointOffsetX = fromOffsetX;
    line.toJunctionPoint = RGJunctionPoint.left;
});
await graphInstance.setJsonData(myJsonData);

This update path is the core runtime technique: it changes already rendered connectors instead of rebuilding the dataset.

const updateMyData = () => {
    graphInstance.getLines().forEach(line => {
        graphInstance.updateLine(line, {
            fromJunctionPointOffsetX: fromOffsetX
        });
    });
};

This node slot is what makes expandable parents read like folders instead of plain rectangles.

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => {
        return (
            <div className={`w-full h-full flex place-items-center gap-2 p-2 ${node.rgChildrenSize > 0 ? 'bg-gray-100 rounded' : ''}`}>
                {
                    node.rgChildrenSize > 0
                        ? <FolderOpen className="w-4 h-4 text-blue-700" />
                        : <div className="w-4 h-4 "></div>
                }

What Makes This Example Distinct

According to the comparison data, this example is not simply another hierarchy viewer. Its distinctive combination is a folder-specific layout, recursive tree-to-RGJsonData preprocessing, pre-load junction normalization, slot-based folder rendering for expandable parents, and one graph-wide slider that updates connector start offsets after mount.

That makes it different from nearby examples in specific ways. Compared with layout-folder2, this example is more focused on file-structure semantics and one reusable routing control than on richer card-like node presentation or spacing relayout controls. Compared with tree-data, it goes further by flattening the source tree into explicit lines, which is what enables both pre-load routing setup and later batch updates on live connectors. Compared with io-tree-layout or layout-tree, the emphasis is not on switching presets or orientations; it is on keeping one stable folder-viewer configuration while tuning how all connectors leave their source nodes.

The comparison data also warns against overstating its uniqueness. It is not the only example that uses the folder layout, and the floating utility window, canvas settings, and image export come from shared demo scaffolding. What stands out here is the focused combination of folder-style presentation and connector-offset control inside a viewer-oriented example.

Where Else This Pattern Applies

This pattern transfers well to product areas that need a readable nested structure without opening a full graph editor. Typical candidates include cloud storage browsers, permission inheritance trees, product category trees, document or page outlines, build artifact folders, and code ownership hierarchies.

The preprocessing pattern is also reusable when backend data arrives as nested objects but the UI needs line-level routing control after render. Flattening the tree into explicit nodes and lines gives later features a clean extension point for connector geometry, highlighting, or conditional line updates without changing the original domain shape.