JavaScript is required

插槽自定义节点内容与样式

这个示例渲染一棵从左到右的树图,用自定义 React 插槽内容替换默认节点主体。单一静态数据集混合了轮播卡片、视频卡片、按钮节点、圆形徽章和玻璃风标签,并通过共享悬浮面板提供运行时画布设置与图片导出。

树形图中基于插槽的节点内容与样式

这个示例构建了什么

这个示例构建了一个从左到右布局的树形图,其节点并不共用同一种视觉模板。相反,图中通过单一节点插槽渲染多种不同的节点主体:带轮播的大型根节点卡片、视频卡片、矩形按钮节点、圆形徽章,以及半透明的胶囊标签。

最终可见效果是一个全高展示画布,背景为黄绿色,连接线为半透明白色,并带有一个悬浮的白色工具窗口。用户可以像查看器一样浏览图谱,与嵌入节点中的控件交互,打开悬浮设置面板,切换画布交互模式,并将当前图谱导出为图片。

最重要的点并不在于这棵树本身。这个示例展示了:一个静态的 relation-graph 数据集,如何通过 RGSlotOnNode 驱动异构的 React 节点内容;同时 SCSS 如何把选中态样式重定向到已渲染的插槽内容上,而不是库默认的节点主体。

数据是如何组织的

图数据以内联方式创建为一个 RGJsonData 对象,包含 rootIdnodeslines。这里没有 fetch,没有异步转换流程,也没有加载后的动态图编辑。结构很直接:

  • nodes 保存图中的身份信息和展示提示。
  • lines 定义树的关系。
  • type 决定 MyNodeContent 应该渲染哪一种自定义节点主体。
  • data.buttonText 携带按钮标签这种按节点区分的负载数据。
  • 即使可见主体主要是自定义插槽内容,nodeShape 仍然会影响 relation-graph 的几何计算。

在调用 setJsonData() 之前并没有单独的预处理步骤。这个示例在组装 JSON 字面量时就直接给节点打上标签,因此图谱加载后,插槽渲染器可以立即基于这些元数据进行分支。

在真实应用中,同样的模式可以映射到组织角色、工作流状态、富媒体内容卡片、KPI 徽章、告警节点或操作节点等场景,在这些场景中,每种节点类型都需要不同的 HTML 结构,但仍然属于同一个图数据集。

relation-graph 是如何使用的

这个图将 relation-graph 用作面向查看的树布局。MyGraph 配置了一个 tree 布局,使其从左向右生长,并采用较大的水平间距与更紧凑的垂直间距,从而让这些混合节点主体保持可读性。选项中还通过将节点填充设为透明、默认边框宽度设为 0,去掉了大部分默认节点外观,使插槽渲染出的 React 内容来定义最终视觉效果。

RelationGraph 被包裹在 RGProvider 中,示例通过 RGHooks.useGraphInstance() 读取实时图实例。该实例用于一次性的初始化流程:通过 setJsonData(...) 加载数据,将视口移动到中心,然后调用 zoomToFit()。同一个 hook 也在共享的悬浮辅助窗口中复用,用于更新交互选项并执行图片导出流程。

核心定制点是 RGSlotOnNode。每个节点都会经过同一个插槽函数,它会把当前节点对象传给 MyNodeContent。该组件根据 node.type 分支并渲染:

  • 根节点的轮播卡片,
  • 内置媒体控制的视频卡片,
  • my-button 的黑色操作按钮,
  • my-circle 的半透明圆形徽章,
  • 其余节点的玻璃风格胶囊标签。

SCSS 补完了这套定制。样式表为工具栏提供半透明白色外观,去掉默认选中节点阴影,并把高亮环应用到自定义插槽包装层上。这一点很重要,因为当前可见的节点主体已经不再是 relation-graph 默认的节点框。

这里没有图编辑流程。这个示例明确处于查看模式,运行时可修改的选项仅限于画布交互行为和导出支持。

关键交互

  • 根节点包含一个带有 Previous 和 Next 控件的轮播组件,证明交互式 React 小部件可以放进节点主体中。
  • 视频节点暴露原生媒体控制,说明插槽内容可以包含富 HTML 媒体,而不仅仅是文本或图标。
  • 按钮节点在节点内容内部处理 DOM 点击事件,并记录当前节点 id。这个行为虽然很小,但它展示了自定义节点小部件可以拥有自己的事件处理逻辑。
  • 悬浮工具窗口可以被拖动、最小化,并切换到设置覆盖层。
  • 设置覆盖层会在实时图实例上修改 wheelEventActiondragEventAction,因此用户无需重新加载数据,就能在滚动、缩放、选择、移动或无画布响应之间切换。
  • 同一个覆盖层还可以通过预处理图 DOM、使用 modern-screenshot 进行捕获、下载 blob,然后恢复图状态的方式,将图谱导出为图片。

关键代码片段

这段代码展示了:该示例依赖 relation-graph 的布局与选项调优,来在一个展示型画布上承载自定义节点内容。

const graphOptions: RGOptions = {
    debug: true,
    backgroundColor: 'rgb(101, 163, 13)',
    defaultLineColor: 'rgba(255, 255, 255, 0.6)',
    defaultNodeColor: 'transparent',
    defaultNodeBorderWidth: 0,
    defaultNodeShape: RGNodeShape.rect,
    toolBarDirection: 'h',
    toolBarPositionH: 'right',
    toolBarPositionV: 'bottom',
    defaultPolyLineRadius: 10,
    defaultLineShape: RGLineShape.StandardCurve,
    defaultJunctionPoint: RGJunctionPoint.lr,
    layout: {
        layoutName: 'tree',
        from: 'left',
        treeNodeGapH: 200,
        treeNodeGapV: 30
    }
};

这段代码展示了:图数据本身就携带了插槽渲染器所使用的节点类型元数据。

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'a', type: 'my-root', nodeShape: RGNodeShape.rect },
        { id: 'b', text: 'b', type: 'my-circle', nodeShape: RGNodeShape.circle },
        // ...
        { id: 'c', text: 'c', type: 'my-video' },
        { id: 'c1', text: 'c1', type: 'my-button', nodeShape: RGNodeShape.rect, data: { buttonText: 'Button 1' } },
        { id: 'c2', text: 'c2', type: 'my-button', nodeShape: RGNodeShape.rect, data: { buttonText: 'Button 2' } }
    ],

这段代码展示了核心的插槽渲染技术:一个节点组件分支成多种不同的节点主体实现。

if (node.type === 'my-root') {
    return (
        <div className="bg-white rounded">
            <SimpleUICarousel contents={nodeContentTypes} width={400} height={200} />
        </div>
    );
} else if (node.type === 'my-video') {
    return (
        <div className="relative h-56 w-72 rounded-lg overflow-hidden">
            <video playsInline className="h-full w-full object-cover overflow-clip-margin-content-box overflow-clip" controls autoPlay loop muted src="https://relation-graph.com/images/video-dribbble.mp4" />

这段代码展示了样式表如何把选中态强调效果,从库默认节点框转移到自定义插槽内容上。

.rg-node-peel.rg-node-checked {
    .rg-node {
        box-shadow: none;

        & > div {
            box-shadow: 0 0 0 8px var(--rg-checked-item-bg-color);
        }
    }
}

这段代码展示了共享辅助窗口如何修改实时图的交互模式,并通过图实例 API 支持图片导出。

<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 }); }}
/>

这个示例的独特之处

与附近的 node-style2node-style3node-style4 等示例相比,这个示例在需求是“替换为异构节点主体”而不是“细化主题样式”时更有价值。对比数据在这一点上很明确:其他示例也可能使用 RGSlotOnNode,但它们通常是在重复同一种视觉系统,或更侧重于宽泛的样式对比。而这个示例则是在一棵紧凑的树中,同时把多种明显不同的内容类型放入节点中。

最强的区分性组合是:

  • 从左到右的树布局,
  • 透明的默认节点主体,
  • 半透明白色曲线连接线,
  • 黄绿色全高画布,
  • 由多个 node.type 驱动的插槽主体,
  • 包裹自定义插槽内容的选中态样式,
  • 复用的悬浮辅助窗口,用于画布设置和导出。

当内置节点渲染器不够用时,这组特性让它比 node-style2 更适合作为起点;而当目标不是广泛的技术对比,而是寻找一个在节点内混合 HTML 与媒体内容的实用模式时,它又比 node 更聚焦。

悬浮辅助窗口应被视为次要部分。对比数据明确提醒不要把它描述成独有能力,因为这套外壳在附近多个示例中都会复用。这里真正有辨识度的经验,是基于插槽的节点内容系统,以及为让选中态和整体外观依然协调而必须做出的样式调整。

这种模式还能应用到哪里

这种模式非常适合那些必须在同一张图里混合多种节点展示方式、但又不改变图数据契约的场景:

  • 产品或内容地图,其中有些节点是标签,有些是媒体预览,有些是操作卡片,
  • 工作流仪表盘,其中审核节点、决策按钮和状态徽章需要不同的 HTML 主体,
  • 教育或叙事类图谱,其中根节点充当特性卡片,子节点充当紧凑标签或检查点,
  • 监控视图,其中告警节点、指标胶囊和下钻操作共享同一拓扑,但不共享相同的渲染需求。

同样的结构也可以继续扩展,例如把内联示例数据替换为 API 数据、把 node.type 扩展为更丰富的渲染注册表,或者将嵌入式节点控件连接到真实业务动作,而不是仅仅做 console logging。