企业集团关系树
这个示例使用静态本地 RGJsonData 渲染以腾讯为中心的企业关系树,并适配到全高 relation-graph 画布。它通过根节点横幅和竖向企业徽章自定义节点渲染,使用 append API 懒加载一个占位分支,并复用共享悬浮辅助窗提供画布设置和图片导出。
支持懒加载分支展开的企业关系树
这个示例构建了什么
这个示例围绕一家核心公司构建了一棵偏重阅读展示的企业关系树,并渲染在全高白色画布上。根公司显示为一条宽横幅,下游公司显示为狭长的纵向徽标,大多数连线都带有来自本地大型数据集的出资比例标签。用户可以查看层级结构,展开一个预先设置的占位分支以追加更多子节点,打开一个悬浮工具窗口,切换画布交互模式,并将当前关系图导出为图片。最值得关注的实现细节是,这棵树通过 relation-graph 的 append API 渐进增长,而不是重新加载整份数据集。
数据如何组织
数据来自 my-data.ts,它以静态内联 RGJsonData 对象的形式由 getMyJsonData() 返回。它包含一个 rootId、一个 nodes 数组和一个 lines 数组。大多数关系都从腾讯根节点流向各个子公司,并带有 出资 比例标签;与此同时,少量上游人员节点通过空标签连入根节点。因此,这个场景读起来更像是一棵企业所有权或关联关系树,而不是经典的员工组织架构图。
在调用 setJsonData(...) 之前,initializeGraph() 会原地预处理这份数据集。它会扫描节点列表,通过强制设置 width 和 height 为 0 来让腾讯根节点自动测量尺寸,并把一个橙色节点改写为懒加载占位节点,添加 data.asyncChild、loaded: false、expandHolderPosition: 'bottom' 和 expanded: false。在生产系统中,同样的结构也可以表示所有权层级、关联公司树、控制方到子公司的视图,或尽调关系总览。
relation-graph 的使用方式
入口文件使用 RGProvider 包裹整个示例,因此主图组件和共享辅助窗口都可以通过 relation-graph hooks 获取当前活动图实例。MyGraph.tsx 使用 RGHooks.useGraphInstance() 来加载准备好的数据集、将视口居中、让整棵树适配屏幕、在懒加载展开期间显示 loading 状态、追加新节点和新连线,并在插入新分支后重新执行布局。
这张图本身使用内置的自上而下 tree 布局,并设置 treeNodeGapV: 100 与 treeNodeGapH: 10。它的选项将 RGLineShape.SimpleOrthogonal 与 RGJunctionPoint.tb 配对使用,因此连接线在向下层级结构中表现为规整的折线连接。节点几何尺寸保持为 slot 测量模式,因为 defaultNodeWidth 和 defaultNodeHeight 都是 0,内置边框被移除,且位于底部的展开控件仍然能与自动重新布局保持兼容。
RGSlotOnNode 是主要的渲染技术。它为腾讯根节点提供了一个专用的横向横幅,而其他所有节点都会渲染为紧凑的纵向徽标,并复用 var(--rg-node-color) 作为填充色和边框色。本地 SCSS 让画布背景保持白色,并为已选中节点添加选中态光晕。悬浮的 DraggableWindow 及其 CanvasSettingsPanel 使用 RGHooks.useGraphStore() 配合 setOptions(...) 来切换当前图的滚轮与拖拽行为,而同一个共享面板也通过 prepareForImageGeneration()、modern-screenshot 和 restoreAfterImageGeneration() 驱动图片导出。
关键交互
展开那个唯一预先准备好的占位节点时,会触发一次性的延迟分支加载。经过一次模拟异步等待后,示例会追加三个子节点和三条带标签的连线,然后重新执行布局。
悬浮辅助窗口可以被拖动、最小化,并通过齿轮按钮切换到设置叠层。
在设置叠层内部,滚轮行为可以在 scroll、zoom 和 none 之间切换,而画布拖拽行为可以在 selection、move 和 none 之间切换。
Download Image 操作会先准备图的 DOM,将当前画布捕获为 blob,下载该图片,然后在事后恢复图状态。
关键代码片段
这个片段展示了核心图配置:自上而下的树布局、正交连接线、底部展开控件,以及依赖 slot 测量的自动尺寸节点。
const graphOptions: RGOptions = {
defaultNodeShape: RGNodeShape.rect,
defaultLineShape: RGLineShape.SimpleOrthogonal,
defaultLineColor: '#666666',
defaultJunctionPoint: RGJunctionPoint.tb,
defaultNodeBorderWidth: 0,
defaultExpandHolderPosition: 'bottom',
reLayoutWhenExpandedOrCollapsed: true,
defaultNodeWidth: 0,
defaultNodeHeight: 0,
defaultNodeColor: '#3B82F6',
layout: {
这个片段说明,该示例在把数据集加载进 relation-graph 之前,会先改写两个特定节点。
const myJsonData: RGJsonData = await getMyJsonData();
myJsonData.nodes.forEach(thisNode => {
if (thisNode.text === '深圳市腾讯计算机系统有限公司') {
thisNode.width = 0;
thisNode.height = 0;
}
if (thisNode.text === '这个节点原本是没有子节点的') {
thisNode.data = { asyncChild: true, loaded: false };
thisNode.expandHolderPosition = 'bottom';
thisNode.expanded = false;
}
});
这个片段展示了分支增长是在展开之后通过 append API 完成的,而不是重建整份图 JSON。
if (nodeObject.data && nodeObject.data.asyncChild === true && nodeObject.data.loaded === false) {
nodeObject.data.loaded = true;
graphInstance.loading('Loading data from remote server...')
setTimeout(async () => {
const newJsonData: RGJsonData = {
nodes: [
{ id: nodeObject.id + '-child-1', text: nodeObject.id + '-Child Node 1' },
{ id: nodeObject.id + '-child-2', text: nodeObject.id + '-Child Node 2' },
{ id: nodeObject.id + '-child-3', text: nodeObject.id + '-Child Node 3' }
],
这个片段展示了 RGSlotOnNode 如何把根节点横幅与纵向子节点徽标区分开来。
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => {
return (
node.text === '深圳市腾讯计算机系统有限公司' ?
<div>
<div className="rounded bg-blue-500 text-white flex items-center justify-center gap-2 px-4 py-4">
<Building2Icon size={40} />
<div className="w-fit text-2xl">{node.text}</div>
</div>
</div>
:
这个片段来自共享辅助面板,展示了实时交互模式控制以及导出操作。
<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 }); }}
/>
const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
if (imageBlob) {
downloadBlob(imageBlob, 'my-image-name');
}
这个示例的独特之处
与附近的业务类示例(如 scene-group 和 investment)相比,这个示例刻意保持更轻量。它让一个核心公司位于向下展开的树顶端,避免了密集交叉连线和布局后清理逻辑,并采用更简单的白色画布展示方式,以及一种固定的节点皮肤模式,而不是多种运行时视觉模式。
与 show-more-nodes-by-page、investment-penetration 和 show-more-nodes-front 这类渐进加载示例相比,这里的懒加载行为被刻意收窄。它只使用一个会加载一次的占位节点,而不是带类型的分支导航、分页、展开后重新居中流程,或隐藏节点揭示策略。
对比数据还表明,这种组合本身并不常见:一个准备好的、带有大量比例标签的所有权风格数据集,自上而下的正交布局,根节点与子节点不同的 slot 样式,通过 addNodes(...) 和 addLines(...) 一次性异步插入分支,以及同页共享的悬浮设置与导出窗口。这使得该示例非常适合作为只读型企业层级查看器的起点,用于在不演变成完整探索工具的前提下提供适度的渐进披露能力。
这一模式还适用于哪里
这一模式同样适用于股权树、关联公司图、子公司所有权视图,以及需要一个主企业层级并带少量延迟展开能力的合规看板。
它也适合偏展示型的关系图查看器,在这些场景里,根实体需要比下游节点更强的视觉识别度,但实现层面仍应依赖 relation-graph 的内置树布局。
这种 append 后再重新布局的方法适用于任何需要按需揭示更多图细节、又不想重建整份数据集的产品,尤其是在页面还需要轻量级查看器工具(如模式切换和截图导出)时。