JavaScript is required

基础自定义节点与缩略图

这个示例构建了一个全高度的 relation graph 查看器,用于展示一个虚构的小型公司网络。完成后的场景会在中间显示一个更大的根节点,并将其渲染为带动画的圆形徽标;周围节点则显示为更小的图标圆点,标签放在节点下方。像 `Invest` 和 `Executive` 这样的连线标签会绘制在连接路径上,同时小地图会始终显示在图视图内部。

构建一个带常驻小地图的基础自定义节点查看器

这个示例构建了什么

这个示例构建了一个全高度的 relation graph 查看器,用于展示一个虚构的小型公司网络。完成后的场景会在中间显示一个更大的根节点,并将其渲染为带动画的圆形徽标;周围节点则显示为更小的图标圆点,标签放在节点下方。像 InvestExecutive 这样的连线标签会绘制在连接路径上,同时小地图会始终显示在图视图内部。

由于图谱会在挂载时完成加载、居中并适配视口,用户一打开就可以直接查看网络。这个示例最重要的教学点并不是那些业务名称本身,而是在一个很小的 React 示例里,紧凑地组合了基于 provider 作用域的图初始化、自定义节点渲染,以及内嵌总览部件。

数据是如何组织的

数据保存在 MyGraph.tsx 中一个内联的 staticJsonData 常量里。它使用标准的 RGJsonData 结构,包含 rootIdnodeslines。当前分析到的载荷包含 19 个节点和 18 条带标签的连线。大多数节点使用全局默认尺寸 60 x 60,而根节点则在数据中直接设置了 width: 100height: 100,以便自定义根节点渲染器拥有更大的展示空间。

在调用 setJsonData() 之前没有任何预处理步骤。组件直接在代码中声明最终的图数据载荷,并在启动期间把它直接传给图实例。这里可复用的部分是按节点存储元数据的模式:每个节点都在 data.myicon 中保存一个图标键,自定义节点组件再把这个字段转换成不同的 Lucide 图标。在真实应用中,同样的结构可以表示组织、系统、人员、资产或工作流步骤,而 data.myicon 也可以替换为类型、状态或分类信息。

relation-graph 是如何使用的

index.tsxRGProvider 包裹整个示例,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 从 provider 上下文中访问当前活动实例。一个在挂载时执行的 useEffect() 会运行 initializeGraph(),加载内联 JSON,然后调用 moveToCenter()zoomToFit(),从而让查看器在打开时就处于可用状态,而不需要用户额外操作。

图配置保持得很精简,但表达得很明确。示例启用了调试模式,使用圆形节点,把 defaultNodeWidthdefaultNodeHeight 设置为 60,让连线文本绘制在路径上,并配置了内置的 center 布局,参数为 maxLayoutTimes: 3000。它还设置了 defaultExpandHolderPosition: 'right'reLayoutWhenExpandedOrCollapsed: true,尽管当前这个本地示例仍然是只读查看器,而不是编辑器。

主要定制来自两个 relation-graph 插槽。RGSlotOnNodeCustomNode 替换了默认节点主体,后者会在专用根节点模板和基于图标的子节点模板之间分支。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-2node-menu 相比,这个示例通过 RGMiniView 使用 RGSlotOnView 作为被动导航辅助,而不是作为上下文操作界面。与 node-tips 相比,它强调的是常驻总览导航和更强的视觉识别,而不是悬停查看。与 toolbar-buttons 相比,它的主要教学重点是自定义节点组合,而不是自定义图谱外壳或小地图开关。

这里需要把结论保持在较窄范围内。对比记录并不支持声称这是唯一使用自定义节点插槽的示例,也不支持声称这是唯一带小地图的示例。它真正支持的说法是:当团队希望找到一个足够小的基线,同时把这些部分组合在一起,又不额外引入菜单状态、提示状态或工具栏状态时,这个示例是一个特别紧凑的参考。

这种模式还能用于哪里

这种模式很适合轻量级归属关系图、服务依赖查看器、生态关系图、组织结构图,以及产品关系类页面,在这些场景中,当前最直接的需求是一个清晰、带品牌感的查看器,而不是编辑器。保持启动序列不变的前提下,同样的内联数据加插槽结构也可以替换为 API 数据。

它也可以扩展到更丰富的界面。团队可以保留相同的 RGProvider 设置、图实例初始化流程和 RGSlotOnNode 定制方式,然后在后续再加入详情抽屉、悬停面板、过滤控件,或业务特定的点击动作。这个示例之所以有用,恰恰是因为这些扩展目前都还没有实现,因此这个基线仍然很容易复制和扩展。