JavaScript is required

企业集团关系树

这个示例使用静态本地 RGJsonData 渲染以腾讯为中心的企业关系树,并适配到全高 relation-graph 画布。它通过根节点横幅和竖向企业徽章自定义节点渲染,使用 append API 懒加载一个占位分支,并复用共享悬浮辅助窗提供画布设置和图片导出。

支持懒加载分支展开的企业关系树

这个示例构建了什么

这个示例围绕一家核心公司构建了一棵偏重阅读展示的企业关系树,并渲染在全高白色画布上。根公司显示为一条宽横幅,下游公司显示为狭长的纵向徽标,大多数连线都带有来自本地大型数据集的出资比例标签。用户可以查看层级结构,展开一个预先设置的占位分支以追加更多子节点,打开一个悬浮工具窗口,切换画布交互模式,并将当前关系图导出为图片。最值得关注的实现细节是,这棵树通过 relation-graph 的 append API 渐进增长,而不是重新加载整份数据集。

数据如何组织

数据来自 my-data.ts,它以静态内联 RGJsonData 对象的形式由 getMyJsonData() 返回。它包含一个 rootId、一个 nodes 数组和一个 lines 数组。大多数关系都从腾讯根节点流向各个子公司,并带有 出资 比例标签;与此同时,少量上游人员节点通过空标签连入根节点。因此,这个场景读起来更像是一棵企业所有权或关联关系树,而不是经典的员工组织架构图。

在调用 setJsonData(...) 之前,initializeGraph() 会原地预处理这份数据集。它会扫描节点列表,通过强制设置 widthheight0 来让腾讯根节点自动测量尺寸,并把一个橙色节点改写为懒加载占位节点,添加 data.asyncChildloaded: falseexpandHolderPosition: 'bottom'expanded: false。在生产系统中,同样的结构也可以表示所有权层级、关联公司树、控制方到子公司的视图,或尽调关系总览。

relation-graph 的使用方式

入口文件使用 RGProvider 包裹整个示例,因此主图组件和共享辅助窗口都可以通过 relation-graph hooks 获取当前活动图实例。MyGraph.tsx 使用 RGHooks.useGraphInstance() 来加载准备好的数据集、将视口居中、让整棵树适配屏幕、在懒加载展开期间显示 loading 状态、追加新节点和新连线,并在插入新分支后重新执行布局。

这张图本身使用内置的自上而下 tree 布局,并设置 treeNodeGapV: 100treeNodeGapH: 10。它的选项将 RGLineShape.SimpleOrthogonalRGJunctionPoint.tb 配对使用,因此连接线在向下层级结构中表现为规整的折线连接。节点几何尺寸保持为 slot 测量模式,因为 defaultNodeWidthdefaultNodeHeight 都是 0,内置边框被移除,且位于底部的展开控件仍然能与自动重新布局保持兼容。

RGSlotOnNode 是主要的渲染技术。它为腾讯根节点提供了一个专用的横向横幅,而其他所有节点都会渲染为紧凑的纵向徽标,并复用 var(--rg-node-color) 作为填充色和边框色。本地 SCSS 让画布背景保持白色,并为已选中节点添加选中态光晕。悬浮的 DraggableWindow 及其 CanvasSettingsPanel 使用 RGHooks.useGraphStore() 配合 setOptions(...) 来切换当前图的滚轮与拖拽行为,而同一个共享面板也通过 prepareForImageGeneration()modern-screenshotrestoreAfterImageGeneration() 驱动图片导出。

关键交互

展开那个唯一预先准备好的占位节点时,会触发一次性的延迟分支加载。经过一次模拟异步等待后,示例会追加三个子节点和三条带标签的连线,然后重新执行布局。

悬浮辅助窗口可以被拖动、最小化,并通过齿轮按钮切换到设置叠层。

在设置叠层内部,滚轮行为可以在 scrollzoomnone 之间切换,而画布拖拽行为可以在 selectionmovenone 之间切换。

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-groupinvestment)相比,这个示例刻意保持更轻量。它让一个核心公司位于向下展开的树顶端,避免了密集交叉连线和布局后清理逻辑,并采用更简单的白色画布展示方式,以及一种固定的节点皮肤模式,而不是多种运行时视觉模式。

show-more-nodes-by-pageinvestment-penetrationshow-more-nodes-front 这类渐进加载示例相比,这里的懒加载行为被刻意收窄。它只使用一个会加载一次的占位节点,而不是带类型的分支导航、分页、展开后重新居中流程,或隐藏节点揭示策略。

对比数据还表明,这种组合本身并不常见:一个准备好的、带有大量比例标签的所有权风格数据集,自上而下的正交布局,根节点与子节点不同的 slot 样式,通过 addNodes(...)addLines(...) 一次性异步插入分支,以及同页共享的悬浮设置与导出窗口。这使得该示例非常适合作为只读型企业层级查看器的起点,用于在不演变成完整探索工具的前提下提供适度的渐进披露能力。

这一模式还适用于哪里

这一模式同样适用于股权树、关联公司图、子公司所有权视图,以及需要一个主企业层级并带少量延迟展开能力的合规看板。

它也适合偏展示型的关系图查看器,在这些场景里,根实体需要比下游节点更强的视觉识别度,但实现层面仍应依赖 relation-graph 的内置树布局。

这种 append 后再重新布局的方法适用于任何需要按需揭示更多图细节、又不想重建整份数据集的产品,尤其是在页面还需要轻量级查看器工具(如模式切换和截图导出)时。