Trade Partner Relationship Explorer
This example builds a trade-oriented relationship viewer around one focal company, with inbound categories above the root and outbound categories below it. Expanding a category lazily appends company nodes, while checked company chips can open a partner card and root controls can hide the upper or lower branch set independently.
Trade Partner Relationship Explorer with Lazy Branch Expansion
What This Example Builds
This example builds a trade-oriented relationship viewer around one focal company. The center card sits between inbound categories such as Purchased Products, Supplier, and Country of Origin, and outbound categories such as Supply Products, Buyer, and Country of Destination.
Users can zoom and drag the canvas, expand category nodes to load partner companies on demand, hide either the upper or lower half of the graph from the center node, and inspect a loaded company through an anchored partner card. The main value is not generic tree rendering. It is a compact business viewer that combines branch growth, branch filtering, and node-level inspection inside one graph.
How the Data Is Organized
The initial graph is seeded inline inside initializeGraph() as two arrays: seven nodes and six lines. One center node stores UI state in data.expandedUpSide and data.expandedDownSide, while the six category nodes declare type, fixed widths, and expandHolderPosition so the graph starts as a split inbound-versus-outbound tree.
Lazy-loaded data comes from fetchMockData(componyId), which returns an RGJsonData fragment with nodes and lines. Each fetched child is a company node with the c-company class name, and the expand handler copies the parent node’s x and y position into every new child before insertion so the next layout pass can animate outward from the expanded branch.
In a real business system, the same structure could be backed by supplier lists, buyer relationships, country-level trade dimensions, product catalogs, or customs records. The demo mock API keeps the payload small, but the seed-plus-fragment pattern already matches incremental loading from a real backend.
How relation-graph Is Used
The page mounts inside RGProvider, and RGHooks.useGraphInstance() drives all graph mutations. The graph uses the tree layout with from: 'top', treeNodeGapH: 10, treeNodeGapV: 120, orthogonal connectors, and top-bottom junction points. That configuration produces a centered vertical structure with inbound categories above the root and outbound categories below it.
RelationGraph binds onNodeExpand so category nodes can append new graph fragments through addNodes(...), addLines(...), and doLayout(). The same graph instance API also bootstraps the first render with addNodes(...), addLines(...), doLayout(), moveToCenter(), and zoomToFit(), then manages lazy-load feedback with loading('Loading Data...'), sleep(400), and clearLoading().
The main customization point is RGSlotOnNode. It replaces the default node body so the center node can render two custom visibility toggles, category nodes can render as styled business cards, and lazily inserted company nodes can render through MyComponyDetail. The local SCSS file then recolors default expand buttons to blue and reduces text size for .c-company nodes.
Key Interactions
- Expanding one of the seeded category nodes triggers a one-time async load. A loading overlay appears, five company nodes are generated, and the graph relayouts after insertion.
- The center node has separate top and bottom toggle buttons. They do not remove data; they hide related nodes by filtering
getNodeRelatedNodes(...)on negative or positivelot.levelvalues. - Wheel input zooms the canvas and drag input moves it, so the viewer behaves like an exploratory dashboard rather than a static diagram.
- When a company node enters the slot’s
checkedstate,PartnerCardappears below that node with fallback partner metrics and product tags.
Key Code Fragments
This fragment shows the layout and canvas options that shape the inbound-versus-outbound tree.
const graphOptions: RGOptions = {
layout: {
layoutName: 'tree',
from: 'top',
treeNodeGapH: 10,
treeNodeGapV: 120,
},
defaultLineShape: RGLineShape.StandardOrthogonal,
defaultJunctionPoint: RGJunctionPoint.tb,
wheelEventAction: 'zoom',
dragEventAction: 'move',
};
This fragment shows how the demo seeds one center company and separate upper and lower category nodes before the first layout pass.
const nodes = [
{
id: 'center', text: 'HONGKONG SLKH CASTING CO LTD',
color: '#1a73e8', fontColor: '#ffffff', borderColor: '#1a73e8',
width: 300, height: 50,
data: { expandedUpSide: true, expandedDownSide: true }
},
{ id: 'in1', text: 'Purchased Products', width: 200, expandHolderPosition: 'top', expanded: false, borderColor: '#f59e0b', color: '#ffffff', fontColor: '#334155', type: 'input' },
{ id: 'in2', text: 'Supplier', width: 200, expandHolderPosition: 'top', expanded: false, borderColor: '#3b82f6', color: '#ffffff', fontColor: '#334155', type: 'input' },
{ id: 'out1', text: 'Supply Products', width: 200, expandHolderPosition: 'bottom', expanded: false, borderColor: '#f59e0b', color: '#ffffff', fontColor: '#334155', type: 'output' },
{ id: 'out2', text: 'Buyer', width: 200, expandHolderPosition: 'bottom', expanded: false, borderColor: '#3b82f6', color: '#ffffff', fontColor: '#334155', type: 'output' },
];
This fragment shows the one-time lazy expansion path, including loading feedback, position seeding, incremental insertion, and relayout.
const onNodeExpand = async (node: RGNode) => {
if (!node.data.dataLoaded) {
graphInstance.loading('Loading Data...');
node.data.dataLoaded = true;
const newNodeAndLines = await fetchMockData(node.id);
newNodeAndLines.nodes.forEach((n: JsonNode) => {
n.x = node.x;
n.y = node.y;
});
graphInstance.addNodes(newNodeAndLines.nodes);
graphInstance.addLines(newNodeAndLines.lines);
await graphInstance.sleep(400);
graphInstance.clearLoading();
await graphInstance.doLayout();
}
}
This fragment shows how the center node hides one side of the graph by updating related nodes instead of rebuilding the dataset.
const toggleRootUp = (node: RGNode) => {
const newExpanded = !node.data.expandedUpSide;
graphInstance.updateNodeData(node, {
expandedUpSide: newExpanded
});
const relatedNodes = graphInstance.getNodeRelatedNodes(node);
const leftNodes = relatedNodes.filter(n => n.lot.level < 0);
leftNodes.forEach(node => {
graphInstance.updateNode(node, {
hidden: newExpanded === false
});
});
}
This fragment shows how the custom node slot turns a company node into both a compact chip and a trigger for the floating partner overlay.
const MyComponyDetail: React.FC<{ node: RGNode, checked?: boolean }> = ({ node, checked }) => {
const partnerInfo = node.data.info || {
name: "JINAN MEIDE CASTING CO LTD",
country: "China",
yearsActive: 8,
};
return (
<div className="flex items-center justify-center px-4 py-2 text-sm font-medium transition-all gap-2">
<HotelIcon className="text-blue-500 shrink-0" size={16} />
<div className="rg-node-text">{node.text}</div>
<div className="absolute top-[60px]">
{checked && <PartnerCard partner={partnerInfo} />}
</div>
</div>
)
}
What Makes This Example Distinct
The comparison data does not support treating any single mechanism here as unique. Other examples also demonstrate lazy expansion, custom node rendering, or root-side branch controls. What stands out is the combination.
Compared with expand-button, this example embeds lazy expansion inside a trade-specific dashboard scene instead of presenting the mechanic in isolation. Compared with multiple-expand-buttons, it adds async graph growth and a checked-node inspection card on top of the root-level branch toggles. Compared with investment and investment-penetration, the emphasis shifts away from ownership or navigation flows toward trade-dimension exploration and partner inspection.
That makes this example a strong starting point when the requirement is a focal company in the middle, domain categories around it, deferred branch loading, and a richer in-canvas detail view for selected partners.
Where Else This Pattern Applies
This pattern transfers well to supply-chain exploration, procurement review tools, export and import investigation screens, and B2B partner due-diligence dashboards. The same split root can represent upstream versus downstream relationships, domestic versus overseas markets, or inbound versus outbound logistics categories.
It is also a useful template when the full relationship graph is too large to preload. In those cases, the current structure can be reused with real APIs so each branch loads only when the analyst expands a category, while the checked-node overlay becomes a compact place for partner metrics, compliance notes, or product summaries.