JavaScript is required

显示分支中的全部预加载节点

这个示例展示如何预加载多个企业关系分支、先隐藏大多数成员,再通过图内合成的 `Show More(...)` 节点一次性展开整条分支。它还演示了类型化节点插槽渲染、左到右正交树布局,以及用于交互调参和图片导出的共享悬浮设置面板。

通过图内 Show More 节点显示预加载的分支成员

这个示例构建了什么

这个示例围绕一家焦点公司构建了一个偏查看器风格的企业关系树。画布上会显示一个大型蓝色根卡片、四个用于投资和股东类别的类型化分支控制节点、每个分支下紧凑呈现的首批公司节点,以及当某个分支仍有隐藏成员时显示的蓝色 Show More(...) 芯片。

核心交互是在分支内部展开内容,而不是获取新数据。所有分支成员都会在启动时准备好,但其中大部分会保持隐藏,直到用户点击该分支中的合成显示节点。图上方的共享浮动窗口还提供帮助文本、设置面板和图片导出功能。

数据是如何组织的

图从一个内联的 RGJsonData 种子开始,其中包含一个根节点和四个分支节点。每个分支节点都带有类似 investment-buttonshareholder-buttondata.myType 值,初始的 lines 数组则将根节点连接到这四个类型化分类节点。

在图完整显示之前,appendTypeData 会调用本地的 loadMockCompanys(myType) 辅助函数来生成特定分支的公司数组。当前这个辅助函数分别返回固定大小为 834512123 的分支数据,因此该示例表现得像一个稠密但完全已知的数据集,而不是远程 API 拉取。

每个生成的公司节点都会被标记为 data.ownerType = myType,这样后续显示处理器就能找到某个分支下所有隐藏成员。追加步骤还会把一个合成的 more-btn 节点注入到同一分支数据中,并把父分支坐标复制到每个新节点上,这样后续追加动画就会从分支位置开始,而不是从任意坐标开始。

在真实应用中,同样的结构可以表示某家公司当前投资、历史投资、当前股东和历史股东。更广泛地说,它适用于任何已预加载的层级结构,在这些结构中,一个父分类拥有一长列同级记录,但不应该一次性全部显示。

relation-graph 是如何使用的

RGProvider 提供 relation-graph 上下文,而 RGHooks.useGraphInstance() 则驱动 MyGraph 和共享设置面板中的主流程。该组件先通过 setJsonData(...) 初始化图,再通过 addNodes(...)addLines(...) 追加分支成员,随后使用 sleep(400) 等待渲染,调用 doLayout() 重新布局,最后使用 moveToCenter()zoomToFit() 将结果居中并缩放到合适范围。

布局采用从左到右的树形布局,设置包括 layoutName: 'tree'from: 'left'、正交连接线、treeNodeGapH: 100treeNodeGapV: 10alignItemsX: 'start' 以及 alignParentItemsX: 'end'。这些配置会生成一个横向展开的企业树,使得分支展开后密集的同级节点仍然易于阅读。

这个示例大量依赖插槽渲染。RGSlotOnNode 会为四种节点角色提供不同渲染方式:根卡片、类型化分支控制节点、合成的 Show More(...) 动作节点,以及普通公司胶囊节点。图本身保持在查看模式,但这个插槽让这些控制节点看起来像图的一部分,而不是外部页面 UI。

有多个实例 API 在为显示交互提供支持。getNodes() 用来找到所属分支,并在后续按 ownerType 过滤节点;updateNode(...) 用来清除 hiddenremoveNodeById(...) 用来删除显示芯片;点击画布时 clearChecked() 会重置选中样式;共享的 CanvasSettingsPanel 则通过 setOptions(...)prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 来完成运行时交互调整和导出。

样式分为 relation-graph 配置和本地 SCSS 覆盖两部分。配置中设置了白色默认节点颜色、矩形节点、右侧展开锚点、左右连接点以及带圆角的正交折线。SCSS 则补充了平铺分析背景、选中态强调、蓝色分支控制节点、蓝色显示芯片以及白色圆角公司胶囊节点。

关键交互

  • 点击某个 Show More(...) 节点会显示该分支下所有隐藏成员,移除该动作节点,并重新执行布局。
  • 各个分支分类仍然参与 relation-graph 内建的展开与折叠行为,并会在这些状态变化后重新布局。
  • 点击画布空白区域会清除当前节点和连线的选中样式。
  • 浮动帮助窗口可以被拖动、最小化,并切换成一个可修改滚轮和拖拽行为的设置面板。
  • 同一个共享设置面板还可以将当前图画布导出为图片。
  • 在这个演示中,普通节点点击主要用于信息展示;自定义的显示流程则绑定在合成的 more-btn 节点上。

关键代码片段

这个片段展示了在追加任何公司列表之前,图会先以一个焦点公司和四个类型化分支节点作为初始数据。

const myJsonData: RGJsonData = {
    rootId: 'root',
    nodes: [
        { id: 'root', text: 'Tian Technology Co., Ltd.', expandHolderPosition: 'hide', data: { myType: 'root' } },
        { id: 'root-invs', text: 'Investment', disablePointEvent: true, data: { myType: 'investment-button', childrenLoaded: false } },
        { id: 'root-history-invs', text: 'Historical Investment', data: { myType: 'historical-investment-button', childrenLoaded: false } },
        { id: 'root-sh', text: 'Share Holder', disablePointEvent: true, data: { myType: 'shareholder-button', childrenLoaded: false } },
        { id: 'root-history-shareholder', text: 'Historical Share Holder', data: { myType: 'historical-shareholder-button', childrenLoaded: false } }
    ],

这个片段展示了分支成员如何被预加载、按所属类型打标,并在初始可见部分之后隐藏。

const defaultVisibleCount = 3;
const allItems = await loadMockCompanys(myType);
const total = allItems.length;
const thisTypeJsonData: RGJsonData = {
    nodes: [],
    lines: []
};
for (const entItem of allItems) {
    const companyNode: JsonNode = { id: entItem.companyId, text: entItem.companyName, data: { ownerType: myType } };
    if (thisTypeJsonData.nodes.length > defaultVisibleCount) {
        companyNode.hidden = true;
    }

这个片段展示了显示控制本身也是一个被加入到分支结构中的合成图节点。

const loadNextPageButtonNode = {
    id: loadNextPageButtonNodeId, text: `Show More(${remainingItemCount})`,
    data: { myType: 'more-btn', loadType: myType, fromIndex: (fromIndex + 1) }
};
currentPageGraphJsonData.nodes.push(loadNextPageButtonNode);
currentPageGraphJsonData.lines.push({ from: typeNodeId, to: loadNextPageButtonNode.id });

这个片段展示了点击该合成动作节点后,如何将该分支下所有暂存节点取消隐藏,并重新布局图。

const showAllNodes = async(buttonNode: RGNode) => {
    const myType = buttonNode.data.loadType;
    const typeNodes = graphInstance.getNodes().filter(n => n.data.ownerType === myType);
    typeNodes.forEach(n => {
        graphInstance.updateNode(n.id, { hidden: false });
    });
    graphInstance.removeNodeById(buttonNode.id);
    await graphInstance.doLayout();
};

这个片段展示了 RGSlotOnNode 如何将显示节点渲染为一个可点击、原生嵌入图中的控制项,而不是普通节点文本。

{myType === 'more-btn' && (
  <div className="my-node more-btn px-2" onClick={() => {showAllNodes(node)}}>
    {node.text}
  </div>
)}
{!myType && (
  <div className="my-node">
    {node.text}
  </div>
)}

这个片段展示了共享浮动设置面板如何切换交互模式并将图画布导出为图片。

const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
    graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();

这个示例的独特之处

对比数据表明,这个示例最接近 show-more-nodes-by-pageinvestment-penetrationexpand-graduallyopen-by-level,但它传达的实现要点与这些示例都不同。相较于 show-more-nodes-by-page,这个演示并不是关于分页或懒加载。它会在挂载时预加载全部四组分支数据,将溢出成员保持为隐藏状态,并在不重新获取数据的情况下,一次性显示整个分支。

相较于 investment-penetration,这里的重点也不是通过新获取的片段来扩展分支,或处理分支方向。这个示例的独特模式是,在预先种好的分组内部控制分支密度:分支本身已经已知,但 UI 会先保持紧凑,直到用户要求查看其余内容。

相较于 expand-graduallyopen-by-level,这里的控制界面也更加局部化,并且原生属于图本身。显示动作并不是由全局深度预设或通用的重新打开流程驱动。相反,某个分支会在图内部获得一个专用的 Show More(...) 节点,而该节点会在显示完成后消失。

其稀有特征的组合也强化了这一定位。这个示例把从左到右的正交企业树、类型化分支控制节点、按 ownerType 暂存隐藏节点、带图案的画布,以及通过与其他节点相同的插槽系统渲染的合成“全部显示”节点组合在一起。当数据本身已经可用,但某个分支在初始加载后会立刻变得视觉噪声很大时,这种组合就特别适合作为起点。

这种模式还适用于哪里

这种模式很适合迁移到企业股权查看器、供应商或分销商地图,以及项目依赖树等场景。在这些场景中,完整的分支数据已经本地可用,但很长的同级列表应当在用户请求之前保持紧凑。

它也适用于那些需要图内原生 call-to-action 控件,而不是外部工具栏或侧边面板的仪表盘。当某个操作应当只作用于一个分支,并且在视觉上应当与受影响节点并列存在时,分支级的显示芯片会更容易理解。

另一种扩展方向是审查类工具,在同一屏幕中既需要一个默认紧凑的图、可选的分支显示,又需要快速导出图片。当前演示并未实现特定于工作流的审查动作,但它的隐藏节点暂存机制和浮动工具面板,为这类界面提供了一个实用基础。