视图与画布插槽总览
这个示例在一张图中对比 relation-graph 的三种内置插槽层:`RGSlotOnView`、`RGSlotOnCanvasAbove` 和 `RGSlotOnCanvas`。它使用共享 React 状态、视图插槽文本输入和 canvas-above 开关,展示普通 HTML 在不同图层上的放置方式及可交互性。
在同一张图中比较内置视图槽位与画布槽位
这个示例构建了什么
这个示例构建了一个全高度的 relation graph,同时它也充当一个槽位承载面对比面板。图本身是一个小型静态网络,但真正可见的教学内容,是通过 RGSlotOnView、RGSlotOnCanvasAbove 和 RGSlotOnCanvas 渲染出来的三个大型、带颜色区分的 HTML 面板。
用户可以在视图层面板中的文本输入框里输入内容,然后看到同样的文本出现在另外两个基于画布的面板中。画布上方面板里的一个 Show More Info 按钮会显示额外内容块,因此这个示例同时演示了共享状态,以及图组件持有层中的一个简单条件覆盖层。
这里的重点不是图数据编辑或业务语义。真正重要的结果,是把内置视图槽位与画布槽位如何承载普通交互式 HTML,以一种明确的并排对比方式展示出来。
数据是如何组织的
图数据直接在 initializeGraph() 内联创建,为一个 RGJsonData 对象,其中包含 rootId: '2'、扁平的 nodes 数组和扁平的 lines 数组。每个节点都带有 id、text 和 data.myicon 字段。每条连线也都已经包含各自的 id、from、to 和 text。
在调用 setJsonData() 之前,没有单独的数据预处理步骤。这个对象是直接按照 relation-graph 期望的结构组装好的,然后原样加载到实例中。在真实应用里,同样的结构也可以表示组织关系、系统关系、依赖关系、归属关系,或任何其他小型关系集合,而图本身主要作为分层 UI 的承载面。
relation-graph 的使用方式
index.tsx 用 RGProvider 包裹整个示例,而 MyGraph.tsx 通过 RGHooks.useGraphInstance() 获取当前活动实例。一个只在挂载时运行的 useEffect() 会调用 initializeGraph(),该函数加载内联数据集,并立即调用 moveToCenter() 和 zoomToFit(),让演示从一个规整的初始视图开始。
RelationGraph 本身的配置很轻。唯一显式设置的图选项是 defaultJunctionPoint: RGJunctionPoint.border 和 defaultLineColor: '#666'。在当前审查的源码中,没有显式设置 layout,因此节点排列来自 relation-graph 针对这份数据的默认行为。
实现中的核心细节是,这三个槽位演示都属于同一个 RelationGraph 实例。RGSlotOnView 承载一个带文本输入框的固定说明面板。RGSlotOnCanvasAbove 承载第二个面板,其中包含切换按钮和一个有条件显示的额外内容块。RGSlotOnCanvas 承载第三个面板,它会持续显示镜像后的文本。每个面板都使用 pointer-events-auto,这让普通 HTML 控件在图场景内部依然可以交互。
这里没有使用图编辑 API、节点事件或连线事件。本地 SCSS 文件主要只是提供了空的选择器骨架,因此可见的教学效果主要来自内联绝对定位、鲜艳的背景色,以及这些内置槽位承载面本身。Show More Info 控件是从 DraggableWindow.tsx 导入的共享本地 SimpleUIButton 组件,而不是特殊的 relation-graph 小部件。
关键交互
- 在
RGSlotOnView的输入框中输入内容会更新searchText。 - 当前的
searchText值会再次渲染到RGSlotOnCanvasAbove的展开区域中,以及RGSlotOnCanvas的预览区域中。 - 点击
Show More Info会切换画布上方槽位中的条件内容块。 - 这个示例的结构使用户能够在同一个图场景里,对比一个视图层面板和两个画布层面板,而不需要为每一种承载面分别打开单独的演示。
关键代码片段
这个包装层说明,整个演示都运行在同一个 relation-graph provider 上下文中。
const Demo = () => {
return (
<RGProvider>
<MyGraph />
</RGProvider>
);
};
这段代码表明,该示例通过 hook 获取实例访问能力,并且只设置了极少量的图级选项。
const MyGraph = () => {
const [searchText, setSearchText] = useState('You can use this search box to search for nodes');
const [showBackgroundGraph, setShowBackgroundGraph] = useState(false);
const graphInstance = RGHooks.useGraphInstance();
const graphOptions: RGOptions = {
defaultJunctionPoint: RGJunctionPoint.border,
defaultLineColor: '#666'
};
这个片段证明,图数据载荷是以内联方式组装的,并且使用了扁平的 nodes 和 lines。
const myJsonData: RGJsonData = {
rootId: '2',
nodes: [
{ id: '1', text: 'Node-1', data: { myicon: 'el-icon-star-on' } },
{ id: '2', text: 'Node-2', data: { myicon: 'el-icon-setting' } },
{ id: '3', text: 'Node-3', data: { myicon: 'el-icon-setting' } },
// ... more nodes ...
],
lines: [
{ id: 'l1', from: '7', to: '71', text: 'Investment' },
{ id: 'l2', from: '7', to: '72', text: 'Investment' },
// ... more lines ...
]
};
这个初始化流程就是唯一的图实例工作流:加载数据、居中,然后自适应显示。
if (graphInstance) {
await graphInstance.setJsonData(myJsonData);
await graphInstance.moveToCenter();
await graphInstance.zoomToFit();
}
这个槽位片段展示了视图层面板,以及驱动共享 React 状态的输入框。
<RGSlotOnView>
<div className="pointer-events-auto" style={{ top: '0px', left: '0px', position: 'absolute', width: '600px', border: '#efefef solid 1px', zIndex: 22 }}>
<div style={{ backgroundColor: '#fa7b7e', padding: '10px', width: '100%', fontSize: '18px' }}>This is the view slot(RGSlotOnView)</div>
<div style={{ backgroundColor: '#7a9ef8', padding: '10px', width: '100%', fontSize: '12px' }}>
You can customize the content here, such as putting a search box:
<input type="text" defaultValue={searchText} onChange={(e) => { setSearchText(e.target.value); }} />
</div>
</div>
</RGSlotOnView>
这个片段展示了画布上方槽位如何使用本地按钮来显示额外内容,而该内容复用了同一个文本状态。
<RGSlotOnCanvasAbove>
<div className="pointer-events-auto" style={{ top: '-300px', left: '-300px', position: 'absolute', width: '600px', border: '#efefef solid 1px' }}>
<div style={{ backgroundColor: '#f5df7a', padding: '10px', width: '100%', fontSize: '12px' }}>
You can put anything here, these contents will move and scale with the canvas.
<SimpleUIButton onClick={toggleGraph}>Show More Info</SimpleUIButton>
</div>
{showBackgroundGraph && (
<div className="flex justify-center place-items-center text-2xl text-red-900" style={{ backgroundColor: '#cb7ffd', width: '600px', height: '600px', overflow: 'hidden' }}>
<div>{searchText}</div>
</div>
)}
</div>
</RGSlotOnCanvasAbove>
最后这个槽位会在画布层面板中始终显示镜像文本。
<RGSlotOnCanvas>
<div className="pointer-events-auto" style={{ top: '100px', left: '-100px', position: 'absolute', width: '600px', border: '#efefef solid 1px' }}>
<div style={{ backgroundColor: '#f5df7a', padding: '10px', width: '100%', fontSize: '12px' }}>
You can put anything here, even another graph, these contents will move and scale with the canvas.
</div>
<div className="flex justify-center place-items-center text-2xl text-red-900" style={{ backgroundColor: 'rgba(127,201,253)', width: '600px', height: '400px', overflow: 'hidden' }}>
<div>{searchText}</div>
</div>
</div>
</RGSlotOnCanvas>
这个示例的独特之处
从对比内容本身就能明确看出它的差异点:这个示例的不同寻常之处,在于它把 RGSlotOnView、RGSlotOnCanvasAbove 和 RGSlotOnCanvas 并排放在同一个 RelationGraph 中进行比较。附近的示例,例如 node-menu 和 node-menu-2,也使用了视图层 HTML,但它们关注的是由节点触发的菜单和瞬时覆盖层。而这个示例会同时保持多个教学面板可见,并且不依赖选中节点、连线或画布目标。
它也不同于 scene-network-use-canvas-slot,后者把画布槽位作为一个完整仪表板式组合的一部分来使用。在这里,画布槽位只是三种承载面的对比之一,而且其中的内容被刻意保持得很简单,以便图层行为足够直观。与 customize-fullscreen-action 相比,这里的主要集成经验发生在图容器内部,而不是在外围包装布局中围绕图容器展开。
最有辨识度的特征组合,是一份共享的 React 状态同时流经三个内置槽位承载面:视图槽位输入框、画布上方展开面板,以及画布槽位读出区域。因此,当需求是跨图层协调 HTML 覆盖层,而不是菜单、全屏行为或图编辑时,这个示例会是一个更好的起点。
这种模式还能用在哪里
这种模式适合那些同时需要固定控件和随图移动覆盖层的图页面。比如固定在视口上的搜索框或筛选框、需要随画布一起移动的注释卡片,以及直接放在图场景内部、用于解释缩放或平移行为的说明面板。
它也很适合迁移到监控看板、架构图、关系检查工具和引导演示中。在这些场景里,底层的节点和连线可以来自真实业务数据,而槽位组合模式保持不变:一个状态来源、普通 HTML 控件,以及针对视图级内容和画布级内容分别制定的放置规则。