Corporate Equity Structure Diagram
This example renders a top-down corporate ownership diagram with investors above the focal company and subsidiaries below it. It combines custom company-card node slots, post-layout cleanup for upstream branches, one-time lazy subsidiary expansion, and a shared floating panel for canvas settings and image export.
Corporate Equity Structure Diagram with Lazy Subsidiary Expansion
What This Example Builds
This example builds a read-focused corporate ownership diagram. Investors appear above the focal company, operating subsidiaries appear below it, and each relationship is labeled with an ownership percentage.
The result is more specific than a generic tree demo. Nodes are rendered as company cards, the root company is shown as a blue banner, special companies can display controller or risk ribbons, and the canvas uses a repeated analysis-style background. Users can expand selected subsidiaries to reveal more branches, open the floating settings panel, change canvas interaction behavior, and export the current graph as an image.
The main point of interest is the combination of three behaviors in one viewer: business-specific node rendering, post-layout cleanup for upstream branches, and one-time lazy branch growth.
How the Data Is Organized
The graph data is assembled inline inside initializeGraph() as a single RGJsonData object. The initial payload declares rootId: '0', eleven nodes, and ten ownership lines. The structure already separates upstream investors (a-1, a-2, a) from the focal company (0) and downstream subsidiaries (1 to 5, plus children under 4).
Two subsidiaries are prepared as lazy-loading placeholders before setJsonData(...) runs. Their node data includes remoteChilds: true and loaded: false, which lets the graph show expand controls even though those child companies are not part of the initial dataset.
There is also an important post-load preprocessing step. After relation-graph finishes layout, the code reads node.lot.level from the live nodes, collects upstream and root ids, moves parent-side expand holders to the top, hides the root expand holder, and adjusts line-label placement for investor-side edges.
In a real business system, this same structure can represent shareholders, holding entities, controlled subsidiaries, percentage stakes, and simple company status markers such as controller or cancelled-state badges.
How relation-graph Is Used
The example is wrapped in RGProvider, and the graph itself is driven through RGHooks.useGraphInstance(). The base graph options define a tree layout that grows from top to bottom, keeps columns centered, and uses 20px horizontal spacing with 80px vertical spacing.
Several options shape the ownership-chart presentation. Nodes default to rectangular shapes with no built-in border, links use RGLineShape.SimpleOrthogonal, corners are rounded with defaultPolyLineRadius: 5, and RGJunctionPoint.tb keeps connections anchored on top and bottom junctions. reLayoutWhenExpandedOrCollapsed stays enabled so the layout remains consistent when lazy branches open.
The example does not rely on default node rendering. RGSlotOnNode replaces the node body with a company-card template that checks node.lot.level === 0 to detect the root, applies a full-width root banner style, and conditionally shows controller or risk ribbons from node.data.
The graph instance API is central to the workflow. setJsonData(...) loads the initial structure, getNodes() and getLines() expose computed layout metadata, updateNode(...) and updateLine(...) refine upstream presentation after layout, and addNodes(...), addLines(...), and doLayout() grow the graph when a lazy branch expands. moveToCenter() and zoomToFit() finalize the first view.
The floating helper window is a reused local subcomponent rather than investment-specific graph logic, but it still affects this example’s runtime behavior. Inside that shared component, RGHooks.useGraphStore() reads the current interaction settings, setOptions(...) switches wheel and drag behavior, and the image export flow uses prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration().
Styling is finished with SCSS overrides. The stylesheet adds the tiled canvas background, recolors expand buttons, boxes the percentage labels, and defines the company-card proportions, ribbon badges, checked-state styling, and root-banner variant.
Key Interactions
The most important interaction is branch expansion. When the user expands a node marked with remoteChilds: true and loaded: false, the example shows a loading state, waits two seconds, appends three new subsidiaries and their ownership links, reruns layout, and marks that node as loaded so the same branch is not duplicated later.
The floating helper window adds a second interaction layer. Users can drag the window, minimize it, open a settings overlay, switch wheel behavior between scroll, zoom, and none, switch canvas drag behavior between selection, move, and none, and download the current graph as an image.
There is also an onNodeClick handler, but it only logs the clicked node to the console, so it does not materially change the user-facing behavior.
Key Code Fragments
This fragment establishes the visual baseline: a top-down tree with orthogonal links and automatic relayout on expand or collapse.
const graphOptions: RGOptions = {
defaultExpandHolderPosition: 'bottom',
defaultNodeShape: RGNodeShape.rect,
defaultNodeBorderWidth: 0,
defaultLineShape: RGLineShape.SimpleOrthogonal,
defaultPolyLineRadius: 5,
defaultJunctionPoint: RGJunctionPoint.tb,
reLayoutWhenExpandedOrCollapsed: true,
layout: {
layoutName: 'tree',
from: 'top',
treeNodeGapH: 20,
treeNodeGapV: 80
}
};
This fragment shows that the initial dataset already mixes fixed ownership links with nodes prepared for later lazy expansion.
const myJsonData: RGJsonData = {
rootId: '0',
nodes: [
{ id: 'a-1', text: 'Jack Li', data: { owner: true } },
{ id: 'a-2', text: 'Tom Yang' },
{ id: 'a', text: 'Zeekr Technology Limited' },
{ id: '0', text: 'Zhejiang Zeekr Intelligent Technology Co., Ltd.', width: 300 },
{ id: '1', text: 'Zeekr Automotive (Shanghai) Co., Ltd.', expandHolderPosition: 'bottom', expanded: false, data: { remoteChilds: true, loaded: false } },
{ id: '3', text: 'Zhejiang Zeekr Automotive Sales Co., Ltd.', expandHolderPosition: 'bottom', expanded: false, data: { remoteChilds: true, loaded: false } }
]
};
This fragment proves that the example reads computed node.lot.level values after layout and uses them to treat upstream branches differently from the root and downstream nodes.
graphInstance.getNodes().forEach((node) => {
if (!node.lot) return;
if (node.lot.level < 0) {
if (node.rgChildrenSize > 0) {
graphInstance.updateNode(node.id, {
expandHolderPosition: 'top'
});
}
parentsNodeIds.push(node.id);
} else if (node.lot.level === 0) {
graphInstance.updateNode(node.id, {
expandHolderPosition: 'hide'
});
parentsNodeIds.push(node.id);
}
});
This fragment shows the second part of that cleanup step: investor-side percentage labels are moved toward the start of the orthogonal line.
graphInstance.getLines().forEach(line => {
if (parentsNodeIds.includes(line.from) && parentsNodeIds.includes(line.to)) {
graphInstance.updateLine(line.id, {
placeText: 'start',
polyLineStartDistance: 60
});
}
});
This fragment shows that lazy expansion is implemented as a one-time runtime mutation of the live graph, not as a full dataset rebuild.
if (node.data && node.data.remoteChilds === true && node.data.loaded === false) {
graphInstance.loading('Loading subsidiaries...');
node.data.loaded = true;
setTimeout(() => {
// ... newNodes and newLines are created here ...
graphInstance.addNodes(newNodes);
graphInstance.addLines(newLines);
graphInstance.doLayout();
graphInstance.clearLoading();
}, 2000);
}
This fragment shows how RGSlotOnNode turns default nodes into business cards with root, controller, and risk variants.
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => {
const isRoot = node.lot && node.lot.level === 0;
return (
<div className={`my-industy-node ${isRoot ? 'my-root' : ''}`}>
{!isRoot && node.data?.owner && <div className="my-card-tag my-card-tag-owner">Controller</div>}
{!isRoot && node.data?.risk && <div className="my-card-tag my-card-tag-risk">Cancelled</div>}
<div className="my-card-body">{node.text}</div>
</div>
);
}}
</RGSlotOnNode>
This fragment shows the shared settings panel changing runtime canvas behavior without modifying the ownership data itself.
<SettingRow
label="Wheel Event:"
options={[
{ label: 'Scroll', value: 'scroll' },
{ label: 'Zoom', value: 'zoom' },
{ label: 'None', value: 'none' },
]}
value={wheelMode}
onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>
What Makes This Example Distinct
Compared with nearby examples such as investment-penetration, industry-chain, layout-tree, expand-button, and bothway-tree2, this example is the clearest reference for a fixed top-down equity structure with explicit shareholding percentages. Its core lesson is not generic tree layout, typed branch navigation, or standalone lazy loading in isolation, but the combination of a business-specific ownership view and selective branch growth.
The comparison data also supports a more specific distinction: this example uses post-layout node.lot.level inspection to move upstream expand holders to the top, hide the root expand holder, and shift investor-to-root labels toward the start of those links. That is a more specialized cleanup step than what appears in the simpler lazy-expand and layout-baseline neighbors.
Its rarest combination is the one that matters most for reuse: company-card node slots, boxed percentage labels, a patterned canvas, parent-side label cleanup, and one-time lazy subsidiary loading inside the same viewer. That makes it a stronger starting point for ownership-analysis screens than a generic tree or a purely technical expand-button demo.
Where Else This Pattern Applies
This pattern transfers well to corporate shareholding maps, investment portfolio structures, multi-entity control charts, and compliance review screens that need a focal company surrounded by upstream owners and downstream subsidiaries.
The same approach can also be adapted to non-financial hierarchies whenever the upstream side needs different label placement or expand-control behavior than the downstream side. Examples include franchise ownership structures, partner network control maps, and regulatory entity trees with status badges.
If a product later needs real backend data, the placeholder remoteChilds pattern can be replaced with actual API-driven expansion while keeping the same slot rendering, post-layout cleanup, and export workflow.