基础自定义节点与缩略图
这个示例构建了一个全高度的 relation graph 查看器,用于展示一个虚构的小型公司网络。完成后的场景会在中间显示一个更大的根节点,并将其渲染为带动画的圆形徽标;周围节点则显示为更小的图标圆点,标签放在节点下方。像 `Invest` 和 `Executive` 这样的连线标签会绘制在连接路径上,同时小地图会始终显示在图视图内部。
构建一个带常驻小地图的基础自定义节点查看器
这个示例构建了什么
这个示例构建了一个全高度的 relation graph 查看器,用于展示一个虚构的小型公司网络。完成后的场景会在中间显示一个更大的根节点,并将其渲染为带动画的圆形徽标;周围节点则显示为更小的图标圆点,标签放在节点下方。像 Invest 和 Executive 这样的连线标签会绘制在连接路径上,同时小地图会始终显示在图视图内部。
由于图谱会在挂载时完成加载、居中并适配视口,用户一打开就可以直接查看网络。这个示例最重要的教学点并不是那些业务名称本身,而是在一个很小的 React 示例里,紧凑地组合了基于 provider 作用域的图初始化、自定义节点渲染,以及内嵌总览部件。
数据是如何组织的
数据保存在 MyGraph.tsx 中一个内联的 staticJsonData 常量里。它使用标准的 RGJsonData 结构,包含 rootId、nodes 和 lines。当前分析到的载荷包含 19 个节点和 18 条带标签的连线。大多数节点使用全局默认尺寸 60 x 60,而根节点则在数据中直接设置了 width: 100 和 height: 100,以便自定义根节点渲染器拥有更大的展示空间。
在调用 setJsonData() 之前没有任何预处理步骤。组件直接在代码中声明最终的图数据载荷,并在启动期间把它直接传给图实例。这里可复用的部分是按节点存储元数据的模式:每个节点都在 data.myicon 中保存一个图标键,自定义节点组件再把这个字段转换成不同的 Lucide 图标。在真实应用中,同样的结构可以表示组织、系统、人员、资产或工作流步骤,而 data.myicon 也可以替换为类型、状态或分类信息。
relation-graph 是如何使用的
index.tsx 用 RGProvider 包裹整个示例,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 从 provider 上下文中访问当前活动实例。一个在挂载时执行的 useEffect() 会运行 initializeGraph(),加载内联 JSON,然后调用 moveToCenter() 和 zoomToFit(),从而让查看器在打开时就处于可用状态,而不需要用户额外操作。
图配置保持得很精简,但表达得很明确。示例启用了调试模式,使用圆形节点,把 defaultNodeWidth 和 defaultNodeHeight 设置为 60,让连线文本绘制在路径上,并配置了内置的 center 布局,参数为 maxLayoutTimes: 3000。它还设置了 defaultExpandHolderPosition: 'right' 和 reLayoutWhenExpandedOrCollapsed: true,尽管当前这个本地示例仍然是只读查看器,而不是编辑器。
主要定制来自两个 relation-graph 插槽。RGSlotOnNode 用 CustomNode 替换了默认节点主体,后者会在专用根节点模板和基于图标的子节点模板之间分支。RGSlotOnView 挂载了 RGMiniView,因此小地图直接位于图场景内部,而不是外部面板中。随后,本地 SCSS 又重写了 relation-graph 的选中状态类,为被选中的节点、节点标签、连线描边和连线标签应用了洋红色强调色。
关键交互
- 图谱会在挂载时自行初始化:加载 JSON 数据载荷、让画布居中,并将视口缩放到合适范围。
- 常驻的
RGMiniView为用户提供了始终可见的总览,以及同一图场景中的第二个导航界面。 - 节点点击和连线点击只用于查看。它们会把被点击的对象输出到控制台并返回
true,但不会更新 React state,也不会修改图数据。
关键代码片段
这个包装器表明,示例在使用任何图实例 API 之前,依赖于 provider 上下文。
const MyApp: React.FC = () => {
return (
<RGProvider>
<MyGraph />
</RGProvider>
);
};
这个内联数据集证明了图数据载荷是直接在组件中组装的,同时图标选择来自节点元数据。
const staticJsonData: RGJsonData = {
rootId: '2',
nodes: [
{ id: '2', text: 'Initrode', width: 100, height: 100, data: { myicon: 'delivery_truck' } },
{ id: '1', text: 'Paper Street Soap Co.', data: { myicon: 'fries' } },
{ id: '3', text: 'Cyberdyne Systems', data: { myicon: 'football' } },
// ...
],
lines: [
{ from: '7', to: '71', text: 'Invest' },
这个启动序列是核心的引导模式:在挂载后先加载 JSON,再让图谱居中并适配视口。
const graphInstance = RGHooks.useGraphInstance();
const initializeGraph = async () => {
await graphInstance.setJsonData(staticJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
useEffect(() => {
initializeGraph();
}, []);
这个配置块展示了示例在布局和默认图样式上的选择。
const graphOptions: RGOptions = {
debug: true,
defaultLineShape: 1,
defaultNodeShape: RGNodeShape.circle,
defaultNodeWidth: 60,
defaultNodeHeight: 60,
defaultLineTextOnPath: true,
layout: { layoutName: 'center', maxLayoutTimes: 3000 },
defaultExpandHolderPosition: 'right',
reLayoutWhenExpandedOrCollapsed: true
};
这个图主体表明,自定义节点渲染和小地图都是通过 relation-graph 插槽接入的。
<RelationGraph
options={graphOptions}
onNodeClick={onNodeClick}
onLineClick={onLineClick}
>
<RGSlotOnNode>
{({ node, checked, dragging }: RGNodeSlotProps) => (
<CustomNode node={node} checked={checked} dragging={dragging} />
)}
</RGSlotOnNode>
<RGSlotOnView>
<RGMiniView />
</RGSlotOnView>
</RelationGraph>
CustomNode 中的这个分支证明了根节点和子节点有意采用了不同的视觉模板。
const CustomNode: React.FC<RGNodeSlotProps> = ({ node }) => {
if (node.id === '2') {
return (
<div className="my-node-animation-01 z-[555] h-full w-full rounded-full relative text-lg flex place-items-center justify-center overflow-hidden">
<div className="py-2 w-full text-center text-white bg-gray-100 bg-opacity-40 border-t border-b border-gray-500">
{node.text}
</div>
</div>
);
}
这个子节点片段展示了同一个插槽渲染器如何把 node.data.myicon 转换成基于图标的圆形节点,并在下方放置分离式标签。
return (
<div className="h-full w-full rounded-full flex place-items-center justify-center shadow-md">
<IconSwitcher iconName={node.data?.myicon} size={30} />
<div
className="bg-gray-200 text-black px-2 rounded-lg absolute my-node-text"
style={{ marginTop: '100%', transform: 'translateY(15px)' }}
>
{node.text}
</div>
</div>
);
这个 SCSS 片段证明,示例定制了 relation-graph 的选中状态样式,而不是保留默认的选中颜色。
.rg-node-peel.rg-node-checked {
.rg-node {
color: #f43ce5;
.my-node-text {
color: #f43ce5;
}
}
}
.rg-line-peel.rg-line-checked {
.rg-line {
stroke: #f43ce5;
这个示例的独特之处
对比数据将这个示例描述为一个低门槛起步示例,而不是菜单示例、提示示例或工具栏示例。它最鲜明的特点是:在同一个只读查看器中,以非常紧凑的方式组合了基于 provider 作用域的启动方式、一个内联 RGJsonData 数据载荷、根节点与子节点之间的自定义渲染差异、选中状态样式重写,以及始终挂载的小地图。
与 node-menu-2 和 node-menu 相比,这个示例通过 RGMiniView 使用 RGSlotOnView 作为被动导航辅助,而不是作为上下文操作界面。与 node-tips 相比,它强调的是常驻总览导航和更强的视觉识别,而不是悬停查看。与 toolbar-buttons 相比,它的主要教学重点是自定义节点组合,而不是自定义图谱外壳或小地图开关。
这里需要把结论保持在较窄范围内。对比记录并不支持声称这是唯一使用自定义节点插槽的示例,也不支持声称这是唯一带小地图的示例。它真正支持的说法是:当团队希望找到一个足够小的基线,同时把这些部分组合在一起,又不额外引入菜单状态、提示状态或工具栏状态时,这个示例是一个特别紧凑的参考。
这种模式还能用于哪里
这种模式很适合轻量级归属关系图、服务依赖查看器、生态关系图、组织结构图,以及产品关系类页面,在这些场景中,当前最直接的需求是一个清晰、带品牌感的查看器,而不是编辑器。保持启动序列不变的前提下,同样的内联数据加插槽结构也可以替换为 API 数据。
它也可以扩展到更丰富的界面。团队可以保留相同的 RGProvider 设置、图实例初始化流程和 RGSlotOnNode 定制方式,然后在后续再加入详情抽屉、悬停面板、过滤控件,或业务特定的点击动作。这个示例之所以有用,恰恰是因为这些扩展目前都还没有实现,因此这个基线仍然很容易复制和扩展。