事业群组织架构图
这个示例构建了一个只读组织架构图,包含一个公司根节点和若干一级业务单元。画布会展示一张顶部高管卡片,其下方是四个横向排列的业务单元子树,并配有正交连线、基于层级深度的颜色强调、缩略图,以及围绕图谱的浮动工具窗口。
使用自定义角色卡片构建分组式组织架构图
本示例构建的内容
这个示例构建了一个只读组织架构图,包含一个公司根节点和若干一级业务单元。画布会展示一张顶部高管卡片,其下方是四个横向排列的业务单元子树,并配有正交连线、基于层级深度的颜色强调、缩略图,以及围绕图谱的浮动工具窗口。
用户可以展开或折叠部门,使用内置工具栏和缩略图进行导航,拖动或最小化辅助窗口,在设置面板中切换滚轮和拖拽交互模式,并将图表导出为图片。这个示例的重点并不是普通的树图渲染,而是分组式组合:它将一个源层级结构拆分为多个业务单元树,分别进行布局,并在可见性发生变化时重新组合到一个共享的高管根节点之下。
数据是如何组织的
源数据是一个本地嵌套的 OrgNode 树,包含 id、text、name、title、type 和可选的 children。根节点表示公司负责人,而每个一级子节点表示一个业务单元,例如 Cloud & Infrastructure、AI & Data Science、Smart Device BU 或 Global Business。
这个示例不会直接把这棵树传给 setJsonData(...)。相反,analysisGroups(...) 会提取根节点的一级子节点,并把每个业务单元转换为一个 MyTreeGroup。随后,flatNodeData(...) 会递归地将每棵子树转换为扁平化的 nodes 和 lines 数组,同时在 node.data 中保留 deep、hasChildren、name、title 和 type。在同一轮预处理过程中,buildMyTreeGroup(...) 会添加 data.myGroupId,并把具有子节点的节点的展开按钮移动到右侧。
在真实应用中,同样的结构可以表示企业事业群、区域销售层级、项目组合,或任何一级分支需要独立局部布局、但仍要汇总到同一个共享顶层节点下的分类体系。
relation-graph 的使用方式
index.tsx 使用 RGProvider 包裹整个页面,而 MyGraph.tsx 使用 layoutName: 'fixed' 渲染 RelationGraph。这个固定的外层布局是刻意设计的。示例并没有要求 relation-graph 自动摆放整张组织图,而是通过 RGHooks.useGraphInstance() 以命令式方式加载根节点和所有分组节点,等待 DOM 尺寸稳定,运行自定义布局控制器,添加仅用于显示的根连接线,最后再将视口居中并适配到合适缩放。
分组布局逻辑位于 MyMixTreeLayout 中。loadData(...) 会通过 addNodes(...) 插入公司根节点,通过 addNodes(...) 和 addLines(...) 插入每个扁平化后的分组,根据层级深度为节点着色,并结合 getLinesBetweenNodes(...) 与 updateLine(...) 重写组内连接线的锚点。layoutGroup(...) 会为每个分组创建一个非主布局的 folder 布局,将该分组的根节点固定在原位,并通过 getNodesRectBox(...) 基于前一个分组测量得到的边界来放置后续分组。所有分组摆放完成后,applyAllGroupLayout(...) 会将各业务单元根节点整体下移,并把公司根节点重新居中到合并后的这一排分组上方。
顶部高管节点的连线同样是手动处理的。connectRootToChildrenTreeRoot() 会追加加粗的正交线,并标记为 forDisplayOnly,因此可见的根节点到分组根节点的连接是展示用途的连线,而不是从原始树中加载的普通层级边。运行时更新则聚焦于浏览体验:onNodeExpand 和 onNodeCollapse 会重新执行分组布局,而节点和连线点击仅记录被点击的对象。
relation-graph 的插槽负责可见层的呈现。RGSlotOnNode 会根据节点角色映射到 LeaderCard、BULeaderCard、DirectorCard 或 ManagerCard,使图表呈现为角色卡片界面,而不是默认节点外观。RGSlotOnView 会挂载 RGMiniView 作为缩略图覆盖层。共享的 DraggableWindow 提供一个浮动工具外壳,内含设置抽屉,可通过 setOptions(...) 更新 wheelEventAction 和 dragEventAction,并通过 prepareForImageGeneration() 与 restoreAfterImageGeneration() 支持图片导出。SCSS 文件则进一步完善视觉效果:保持节点内部主体透明、使用节点颜色来样式化展开按钮,并高亮已选中的节点和连线。
关键交互
- 展开或折叠任意分支都会触发
applyAllGroupLayout(),因此某个业务单元中的可见性变化会重新排布同级分组,并让顶部高管节点重新居中。 - 内置工具栏和
RGMiniView让这张较宽的图表更容易查看,而无需额外编写自定义视口控制。 - 浮动辅助窗口可以拖动和最小化,其设置按钮会在同一个窗口内部打开一个覆盖层。
- 设置覆盖层可以将滚轮行为切换为滚动、缩放或无动作,将画布拖拽行为切换为框选、平移或无动作,并提供截图导出。
关键代码片段
这段图配置说明外层画布保持固定,而图表使用透明节点外壳和正交连接线。
const graphOptions: RGOptions = {
debug: false,
layout: {
layoutName: 'fixed'
},
defaultPolyLineRadius: 10,
defaultNodeShape: RGNodeShape.rect,
defaultNodeBorderWidth: 0,
defaultNodeColor: 'transparent',
defaultLineShape: RGLineShape.StandardOrthogonal,
defaultLineWidth: 3,
showToolBar: true,
defaultLineColor: '#666666'
};
这段初始化流程说明示例会先加载数据,再运行自定义组合器,然后在适配视口之前添加从根节点到各分组的展示连线。
const initializeGraph = async () => {
const myTreeJsonData = await getMyTreeJsonData();
myMixTreeLayout.current.setLevelColors(['#7e22ce30', '#2564eb3a', '#71c9053d', '#ea5a0c34']);
await myMixTreeLayout.current.loadData(myTreeJsonData);
await graphInstance.sleep(300);
myMixTreeLayout.current.applyAllGroupLayout();
myMixTreeLayout.current.connectRootToChildrenTreeRoot();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
这段预处理代码展示了每个业务单元子树是如何被扁平化并打上标记的,以便后续布局过程可以按分组查询节点。
const myGroupId = 'G-' + treeData.id;
const rootNodeId = treeData.id;
const { nodes, lines } = flatNodeData([treeData], null);
nodes.forEach(node => {
if (!node.data) node.data = {};
node.data.myGroupId = myGroupId;
if (node.data.hasChildren) {
node.expandHolderPosition = 'right';
}
});
这段代码展示了顺序摆放规则:每个业务单元分组都从前一个分组的边界框出发定位,而不是共享一个自动树布局。
if (group.refs.length > 0) {
const refedMyGroupId = group.refs[0];
const refedGroup = this.getGroupById(refedMyGroupId);
if (!group.layouted) {
this.layoutGroup(refedGroup);
}
const refedGroupView = this.getGroupViewInfo(refedMyGroupId);
groupRootNodeXy.x = refedGroupView.maxX + 100;
}
这段局部布局调用是分组式组合的核心,因为每个业务单元都会以固定根节点的方式单独执行一次 folder 布局。
const layoutOptions: RGLayoutOptions = {
layoutName: 'folder',
from: 'left',
layoutExpansionDirection: 'end',
alignItemsX: 'start',
treeNodeGapV: 20,
treeNodeGapH: -80
};
const myGroupLayout = this.graphInstance.createLayout(layoutOptions as RGLayoutOptions);
myGroupLayout.isMainLayouer = false;
myGroupLayout.layoutOptions.fixedRootNode = true;
myGroupLayout.placeNodes(groupNodes, groupRootNode);
这段连线定义展示了顶部高管节点的连接线是在各分组完成布局之后,作为仅用于展示的连线被追加进去的。
const line: JsonLine = {
id: 'root-to-' + group.rootNodeId,
from: this.rootId,
to: group.rootNodeId,
lineShape: RGLineShape.SimpleOrthogonal,
fromJunctionPoint: RGJunctionPoint.bottom,
toJunctionPoint: RGJunctionPoint.top,
lineWidth: 4,
forDisplayOnly: true,
color: '#666666',
showEndArrow: false
};
这段插槽映射说明可见节点是按角色区分的卡片,而不是默认的节点主体。
<RGSlotOnNode>
{({ node }) => {
const {type, title, name} = node.data;
switch (type) {
case 'leader':
return <LeaderCard name={name} title={title} company={node.text || ''} />;
case 'bu-leader':
return <BULeaderCard name={name} title={title} department={node.text || ''} />
case 'director':
return <DirectorCard name={name} title={title} department={node.text || ''} />
case 'manager':
return <ManagerCard name={name} title={title} team={node.text || ''} />
}
}}
</RGSlotOnNode>
这个示例的独特之处
它最强的特点在于,将业务组织架构场景与分组式自定义布局架构结合在一起。根据对比数据,这个示例并不只是另一个树图浏览器。它会把一个 OrgNode 层级拆分为一个共享的高管根节点和多个一级业务单元分组,在固定外层图中让每个分组各自执行一次从左到右的 folder 布局,然后在布局完成后再追加独立的仅展示用根连接线。
与 mix-layout-tree 相比,它使用了非常相似的分组树技术,但在展示层更进一步。可见节点被渲染为高管、业务单元负责人、总监和经理卡片,置于透明的图形外壳中,因此当分组布局模式需要作为一个可直接展示的组织架构图交付时,这个示例比通用文本节点版本更有参考价值。
与 mix-layout-2 和 multiple-layout-mixing-in-single-canvas 相比,这里的重点不同。那些示例强调 HTML 锚点插槽、隐藏根节点,或多个非连通区域之间的衔接;而这个示例则把整个体验都保留在图节点和图连线之内,通过一个共享层级在分支展开或收起时完成扁平化、分组和围绕可见高管根节点的重新组合。
与 layout-tree 相比,这个示例的独特价值不在于方向切换或内置的整树布局。当一个组织需要拆分为并排的业务单元分组、保留按角色定制的节点卡片,并且仍然要在普通的展开收起变化后重新整理整体构图时,这个示例是更好的起点。
这种模式还适用于哪些场景
- 企业组织浏览器:每个事业部都需要自己的局部间距和卡片设计,但仍要汇总到一个高管视图。
- 项目组合或项目群地图:顶层领域需要在一个统筹节点下渲染为彼此独立的子树簇。
- 区域销售或合作伙伴层级:展开某个分支时,需要触发同级分组的整洁重组。
- 只读的治理、能力或标准层级:需要在宽幅树图周围配备缩略图导航、运行时交互模式控制和截图导出。