JavaScript is required

Node Descendant Subtree Highlight

This example loads a static hierarchy into a centered relation-graph view and highlights the clicked node together with all of its descendants. It demonstrates recursive traversal through runtime node objects, batch node and line updates, canvas-based reset, and a shared floating utility panel for settings and image export.

Highlighting a Node’s Descendant Subtree

What This Example Builds

This example builds a full-viewport hierarchy viewer that starts from a preloaded tree of letter-labeled nodes and lets the user inspect one branch at a time. The graph is shown as a centered network of blue circular nodes with matching blue links, while a floating helper window explains the interaction and exposes shared demo utilities.

The main interaction is subtree focus. When the user clicks a non-leaf node, that node and all of its descendants stay fully visible and turn purple, while unrelated nodes fade into the background. Clicking the canvas clears that temporary focus state and restores the full graph.

The most important idea is that the graph data is not rebuilt after each click. The example keeps one loaded hierarchy in memory and changes node and line state in place through relation-graph instance APIs.

How the Data Is Organized

The dataset is declared inline as one RGJsonData object inside initializeGraph. It uses a flat structure with rootId, a nodes array of { id, text } records, and a lines array of { id, from, to } records. That shape is enough for relation-graph to reconstruct the hierarchy once setJsonData() runs.

There is no preprocessing step before setJsonData(). The only derived logic happens after the graph is already loaded: on node click, the code walks the runtime node object’s node.lot.childs chain to collect descendant ids. That means the example teaches a runtime inspection pattern rather than a data-transformation pipeline.

In real applications, the same structure can represent category trees, organization units, folder hierarchies, product assemblies, or permissions inheritance. The letter labels here are only placeholder data for the interaction pattern.

How relation-graph Is Used

The demo is wrapped in RGProvider so hook-based graph access works inside MyGraph. The graph itself uses the center layout, circular nodes, and fixed 60 by 60 node sizing, with blue as the default node and line color. After the inline data is loaded, the instance recenters and fits the whole graph into view.

The example relies on RGHooks.useGraphInstance() rather than refs. That hook is used in two places: the main graph component uses it for setJsonData(), moveToCenter(), zoomToFit(), getNodes(), getLines(), updateNode(), updateLine(), and dataUpdated(), while the shared DraggableWindow settings panel uses the same graph context to change wheel and drag behavior and to export an image.

No relation-graph slots are rendered in the current source, and there is no editing workflow. This is a read-only viewer that demonstrates post-load graph mutation through instance APIs. The imported SCSS file is only a scaffold with empty rule blocks, so the visible styling comes from graph options and runtime state overrides rather than CSS skinning.

Key Interactions

The primary interaction is node click. A click on a non-leaf node recursively gathers the clicked node plus every descendant, then applies a focus-plus-context state: selected-branch nodes stay opaque and purple, unrelated nodes fade, and only internal lines inside the selected subtree are recolored.

The second important interaction is canvas click. Instead of reloading data, the canvas handler clears the temporary node and line overrides and commits the reset with dataUpdated().

The floating helper window adds secondary utility interactions. It can be dragged, minimized, switched into a settings panel, used to change wheel and drag behavior through setOptions(), and used to export the graph as an image through prepareForImageGeneration() and restoreAfterImageGeneration(). Those controls are useful, but they come from shared demo scaffolding rather than from a subtree-specific implementation.

Key Code Fragments

This fragment shows that the demo is configured as a centered, circular, fixed-size hierarchy before any click logic is applied.

const graphInstance = RGHooks.useGraphInstance();

const graphOptions: RGOptions = {
    layout: {
        layoutName: 'center'
    },
    defaultNodeColor: 'rgb(29, 169, 245)',
    defaultLineColor: 'rgb(29, 169, 245)',
    defaultNodeShape: RGNodeShape.circle,
    defaultNodeWidth: 60,
    defaultNodeHeight: 60
};

This fragment shows the inline data-loading workflow that keeps the whole hierarchy in one graph instance from the start.

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        rootId: 'a',
        nodes: [
            { id: 'a', text: 'a' }, { id: 'b', text: 'b' }, { id: 'b1', text: 'b1' },
            // ...
        ],
        lines: [
            { id: 'l1', from: 'a', to: 'b' }, { id: 'l2', from: 'b', to: 'b1' },
            // ...
        ]
    };
    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

This fragment proves that descendant collection happens from runtime node objects, not from a separate precomputed lookup table.

const deepGetAllChildIds = (node: RGNode, ids: string[] = []): string[] => {
    if (ids.includes(node.id)) return ids;
    ids.push(node.id);
    for (const cNode of node.lot.childs) {
        deepGetAllChildIds(cNode, ids);
    }
    return ids;
};

This fragment shows how the selected subtree stays visible while non-descendants are dimmed in one node-update pass.

const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
    const allChildIds = deepGetAllChildIds(nodeObject);
    graphInstance.getNodes().forEach(node => {
        if (allChildIds.includes(node.id)) {
            graphInstance.updateNode(node, { opacity: 1, color: 'rgb(116,2,173)' });
        } else {
            graphInstance.updateNode(node, { opacity: 0.1, color: undefined });
        }
    });
};

This fragment shows that line emphasis is coordinated with node emphasis and then committed explicitly.

graphInstance.getLines().forEach(line => {
    if (allChildIds.includes(line.from) && allChildIds.includes(line.to)) {
        graphInstance.updateLine(line, { color: 'rgb(116,2,173)' });
    } else {
        graphInstance.updateLine(line, { color: '' });
    }
});
graphInstance.dataUpdated();

This fragment shows the shared settings panel mutating live graph behavior and starting screenshot export through the active graph instance.

const graphInstance = RGHooks.useGraphInstance();
const { options } = RGHooks.useGraphStore();

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={options.wheelEventAction}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

What Makes This Example Distinct

Compared with nearby examples such as line-hightlight-pro, bothway-tree2, and layout-tree, this demo stays on one stable layout and uses its complexity budget on descendant-subtree inspection. Its distinguishing move is the recursive walk over node.lot.childs, followed by a graph-wide node and line update pass that changes interpretation without changing the loaded data.

That makes it different from examples that only mark one clicked object, switch tree orientations, or append new data fragments. The comparison records describe this combination as unusual: a plain centered blue hierarchy, runtime descendant traversal, focus-plus-context fading, and a canvas-reset affordance in one compact viewer.

It is also a stronger reference for branch-level emphasis than demos that highlight just a line or just the clicked node. Here, the entire selected branch stays legible, including internal links, while the rest of the graph remains visible as context instead of disappearing.

Where Else This Pattern Applies

The same pattern transfers well to any tree where users need to inspect one branch without losing the surrounding context. Examples include organization structures, navigation trees, permission inheritance, file-system browsers, product category trees, and bill-of-material hierarchies.

It also works as a starting point for analytic drill-down interactions. A production version could replace the placeholder letters with business records, combine the subtree highlight with side-panel details, or add filters that decide which descendants remain in focus, while keeping the same runtime strategy of walking child nodes and updating the already-loaded graph in place.