JavaScript is required

视图与画布插槽总览

这个示例在一张图中对比 relation-graph 的三种内置插槽层:`RGSlotOnView`、`RGSlotOnCanvasAbove` 和 `RGSlotOnCanvas`。它使用共享 React 状态、视图插槽文本输入和 canvas-above 开关,展示普通 HTML 在不同图层上的放置方式及可交互性。

在同一张图中比较内置视图槽位与画布槽位

这个示例构建了什么

这个示例构建了一个全高度的 relation graph,同时它也充当一个槽位承载面对比面板。图本身是一个小型静态网络,但真正可见的教学内容,是通过 RGSlotOnViewRGSlotOnCanvasAboveRGSlotOnCanvas 渲染出来的三个大型、带颜色区分的 HTML 面板。

用户可以在视图层面板中的文本输入框里输入内容,然后看到同样的文本出现在另外两个基于画布的面板中。画布上方面板里的一个 Show More Info 按钮会显示额外内容块,因此这个示例同时演示了共享状态,以及图组件持有层中的一个简单条件覆盖层。

这里的重点不是图数据编辑或业务语义。真正重要的结果,是把内置视图槽位与画布槽位如何承载普通交互式 HTML,以一种明确的并排对比方式展示出来。

数据是如何组织的

图数据直接在 initializeGraph() 内联创建,为一个 RGJsonData 对象,其中包含 rootId: '2'、扁平的 nodes 数组和扁平的 lines 数组。每个节点都带有 idtextdata.myicon 字段。每条连线也都已经包含各自的 idfromtotext

在调用 setJsonData() 之前,没有单独的数据预处理步骤。这个对象是直接按照 relation-graph 期望的结构组装好的,然后原样加载到实例中。在真实应用里,同样的结构也可以表示组织关系、系统关系、依赖关系、归属关系,或任何其他小型关系集合,而图本身主要作为分层 UI 的承载面。

relation-graph 的使用方式

index.tsxRGProvider 包裹整个示例,而 MyGraph.tsx 通过 RGHooks.useGraphInstance() 获取当前活动实例。一个只在挂载时运行的 useEffect() 会调用 initializeGraph(),该函数加载内联数据集,并立即调用 moveToCenter()zoomToFit(),让演示从一个规整的初始视图开始。

RelationGraph 本身的配置很轻。唯一显式设置的图选项是 defaultJunctionPoint: RGJunctionPoint.borderdefaultLineColor: '#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'
  };

这个片段证明,图数据载荷是以内联方式组装的,并且使用了扁平的 nodeslines

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>

这个示例的独特之处

从对比内容本身就能明确看出它的差异点:这个示例的不同寻常之处,在于它把 RGSlotOnViewRGSlotOnCanvasAboveRGSlotOnCanvas 并排放在同一个 RelationGraph 中进行比较。附近的示例,例如 node-menunode-menu-2,也使用了视图层 HTML,但它们关注的是由节点触发的菜单和瞬时覆盖层。而这个示例会同时保持多个教学面板可见,并且不依赖选中节点、连线或画布目标。

它也不同于 scene-network-use-canvas-slot,后者把画布槽位作为一个完整仪表板式组合的一部分来使用。在这里,画布槽位只是三种承载面的对比之一,而且其中的内容被刻意保持得很简单,以便图层行为足够直观。与 customize-fullscreen-action 相比,这里的主要集成经验发生在图容器内部,而不是在外围包装布局中围绕图容器展开。

最有辨识度的特征组合,是一份共享的 React 状态同时流经三个内置槽位承载面:视图槽位输入框、画布上方展开面板,以及画布槽位读出区域。因此,当需求是跨图层协调 HTML 覆盖层,而不是菜单、全屏行为或图编辑时,这个示例会是一个更好的起点。

这种模式还能用在哪里

这种模式适合那些同时需要固定控件和随图移动覆盖层的图页面。比如固定在视口上的搜索框或筛选框、需要随画布一起移动的注释卡片,以及直接放在图场景内部、用于解释缩放或平移行为的说明面板。

它也很适合迁移到监控看板、架构图、关系检查工具和引导演示中。在这些场景里,底层的节点和连线可以来自真实业务数据,而槽位组合模式保持不变:一个状态来源、普通 HTML 控件,以及针对视图级内容和画布级内容分别制定的放置规则。