JavaScript is required

画布与节点交互限制

这个示例构建了一个全高度的 relation-graph 查看器,并且它在打开时处于受限交互状态。图本身是一个小型的居中层级结构,带有矩形节点和灰色曲线连线,同时画布上方悬浮着一个白色工具窗口。

在居中图查看器中锁定画布与节点交互

这个示例构建了什么

这个示例构建了一个全高度的 relation-graph 查看器,并且它在打开时处于受限交互状态。图本身是一个小型的居中层级结构,带有矩形节点和灰色曲线连线,同时画布上方悬浮着一个白色工具窗口。

用户可以通过三个顶层开关重新启用缩放、画布拖拽和节点拖拽。同一个悬浮窗口也可以被拖动、最小化、展开为设置覆盖层,并用于将当前图导出为图片。

这里的关键点在于初始状态。这个示例并不是一开始就允许自由导航,而是在初始 RGOptions 中声明交互锁定,然后再在运行中的 graph 实例上放开这些限制。

数据是如何组织的

图数据在 initializeGraph 内以内联方式声明为一个 RGJsonData 对象。它使用标准的扁平 relation-graph 结构:一个 rootId、一个由 { id, text } 记录组成的 nodes 数组,以及一个由 { id, from, to } 记录组成的 lines 数组。

在调用 setJsonData() 之前没有任何预处理流程。数据本身唯一的预备工作是每条连线都已经带有显式的 id,而示例在加载完成后会立刻通过 moveToCenter()zoomToFit() 规范化视口。

在真实应用中,同样的结构可以表示组织树、依赖关系提纲、审批层级,或任何要求数据保持可读但初始不可拖拽的嵌入式查看器。

如何使用 relation-graph

index.tsxRGProvider 包裹页面,MyGraph.tsx 则使用 RGHooks.useGraphInstance() 与当前激活的 graph 实例交互。图选项保存在 React state 中,并直接传给 <RelationGraph options={options} />,这使该组件成为一个由状态驱动选项控制的紧凑示例。

配置后的图始终使用同一个 center 布局。它设置了矩形节点、曲线连线、左右上下拐点路由,以及简洁的灰色视觉风格。更重要的是,它在首次渲染时就以 wheelEventAction: 'none'dragEventAction: 'none'disableDragNode: true 启动,因此画布和节点在一开始都被有意锁定。

主要的实例 API 流程简单而直接:setJsonData() 加载内联层级数据,moveToCenter()zoomToFit() 规范化初始视图,而 updateOptions() 则应用来自这三个示例专用开关的局部选项变更。这里没有使用节点插槽、连线插槽、编辑句柄或数据变更。

悬浮控制窗口来自共享的 DraggableWindow 辅助组件。该辅助组件又在 CanvasSettingsPanel 内使用 RGHooks.useGraphStore()RGHooks.useGraphInstance(),在那里读取当前选项状态,通过 setOptions() 切换滚轮和画布拖拽行为,并通过 prepareForImageGeneration()restoreAfterImageGeneration() 导出图像。导入的 SCSS 文件只是一个空的选择器骨架,因此可见效果几乎完全由图选项和共享工具 UI 决定。

关键交互

主要交互是运行时选项切换。悬浮面板中的三个开关通过把局部 RGOptions 合并进本地 React state,并将相同的变更发送给 graphInstance.updateOptions(...),来切换滚轮缩放、画布拖拽和节点拖拽。

第二层交互来自共享工具窗口。它的标题栏可以用来拖动窗口,窗口可以被最小化和恢复,而设置按钮会打开一个覆盖层,其中提供滚轮模式和画布拖拽模式的分段控制。

设置覆盖层还增加了一个更实用的动作:图片导出。它会要求 relation-graph 为截图生成准备画布 DOM,把该 DOM 渲染为一个 blob,下载文件,然后在捕获完成后恢复图状态。

当前源码中没有示例专用的节点点击处理器、选择流程或图编辑流程。这里要讲解的是交互限制与释放,而不是结构编辑或选择状态。

关键代码片段

这段代码说明图从 center 布局启动,并且三项交互限制都已经在初始 options state 中声明好了。

const [options, setOptions] = useState<RGOptions>({
    defaultNodeBorderWidth: 2,
    defaultLineColor: '#666',
    defaultLineWidth: 2,
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardCurve,
    defaultJunctionPoint: RGJunctionPoint.ltrb,
    wheelEventAction: 'none',
    dragEventAction: 'none',
    disableDragNode: true,
    layout: {
        layoutName: 'center'
    }
});

这段代码展示了示例加载到查看器中的扁平内联 RGJsonData 结构。

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'a' }, { id: 'b', text: 'b' }, { id: 'b1', text: 'b1' },
        { id: 'b2', text: 'b2' }, { id: 'b3', text: 'b3' }, { id: 'b4', text: 'b4' },
        // ...
    ],
    lines: [
        { id: 'l1', from: 'a', to: 'b' }, { id: 'l2', from: 'b', to: 'b1' },
        { id: 'l3', from: 'b', to: 'b2' }, { id: 'l4', from: 'b', to: 'b3' },
        // ...
    ]
};

这段代码说明初始化是一次性加载加视口规范化,而不是重复构建周期。

const initializeGraph = async () => {
    // ...build myJsonData
    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

useEffect(() => {
    initializeGraph();
}, []);

这段代码证明该示例通过局部选项补丁来更新实时交互行为。

const handleUpdateOptions = (newOptions: Partial<RGOptions>) => {
    setOptions(prev => ({ ...prev, ...newOptions }));
    graphInstance.updateOptions(newOptions);
};

这段代码展示了示例专用的控制界面,用于禁用缩放、画布拖拽和节点拖拽。

<SimpleUISwitch
    currentValue={options.wheelEventAction === 'none'}
    onChange={(disabled) => {
        handleUpdateOptions({ wheelEventAction: disabled ? 'none' : 'zoom' });
    }}
/>
<SimpleUISwitch
    currentValue={options.dragEventAction === 'none'}
    onChange={(disabled) => {
        handleUpdateOptions({ dragEventAction: disabled ? 'none' : 'move' });
    }}
/>

这段代码展示了共享设置覆盖层如何直接修改当前 graph 实例,并暴露截图导出功能。

const graphInstance = RGHooks.useGraphInstance();
const { options } = RGHooks.useGraphStore();

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={options.wheelEventAction}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

这段代码展示了导出路径:它会临时为图画布准备图片生成环境,然后再恢复回来。

const canvasDom = await graphInstance.prepareForImageGeneration();
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
if (imageBlob) {
    downloadBlob(imageBlob, 'my-image-name');
}
await graphInstance.restoreAfterImageGeneration();

这个示例的独特之处

对比数据把这个示例放在 drag-and-wheel-eventselectionsswitch-layoutzoom 附近,但它的教学目标更窄。与 drag-and-wheel-event 相比,它更强调在初始 RGOptions 中声明锁定的起始状态,并显式切换节点拖拽,而不仅仅是通过共享控件探索滚轮和画布拖拽模式。

selections 相比,它出于相反的目的使用运行时拖拽选项。后者把拖拽行为转变为框选与选中状态反馈,而这个示例则是在用户选择重新启用之前先抑制交互。

switch-layoutzoom 相比,这里的图始终停留在一个稳定的居中查看器上。运行时更新针对的是固定布局上的交互权限,而不是布局播放或预设缩放值。

它最鲜明的组合特征是一个几乎保持默认风格的 center-layout 层级图、初始的画布与节点交互锁定、三个示例级禁用开关,以及共享的可拖动工具窗口。这使它比起选择、编辑或布局实验,更适合作为受保护或 kiosk 风格查看器的起点。

这种模式还适用于哪里

这种模式适合那些在首次渲染时不应对误触鼠标输入作出反应的嵌入式图查看器。典型场景包括仪表盘、 kiosk 屏、引导演示、审批视图、培训演示,以及大型业务应用中的只读图面板。

它也很适合需要按权限释放交互能力的产品。生产环境中的页面可以在开始时禁用所有移动,然后只在模式切换、角色校验或外围 UI 中的某个用户操作之后启用缩放或拖拽,同时保持相同的图数据和同一个已挂载的 graph 实例。