JavaScript is required

双击编辑节点文字

这个示例展示固定布局 relation-graph,用自定义内联编辑器替换默认节点主体。它演示了模式控制的双击改名、键盘与失焦提交/取消行为、`graphInstance.updateNode()` 持久化,以及用于画布设置和图片导出的共享悬浮面板。

固定布局图中的节点文本内联编辑

这个示例构建了什么

这个示例构建了一个固定位置的关系图,节点标签可以在原位直接编辑。界面上可以看到带点状背景的画布、蓝色直线连线和一个浮动辅助窗口,而节点本身则展示了不同的边框、颜色、尺寸、形状以及固定位置设置。

主要的用户操作是维护标签。启用编辑后,双击节点会将普通标签切换为黄色的内联输入框,修改后的文本会直接写回图中,而无需重新加载整份数据集。

关键设计选择在于通过自定义节点插槽替换节点主体。这样一来,示例就可以在组件层级自行控制焦点处理、键盘行为、提交与取消规则,以及全局编辑开关。

数据如何组织

图数据在 initializeGraph() 中以内联方式声明为一个 RGJsonData 对象,其中包含 rootId、扁平的 nodes 数组以及 lines 数组。每条节点记录都带有固定的 xy 坐标,且部分节点还包含可视化覆盖项,例如 borderWidthborderColorfontColorcolorwidthheightfixedclassName,或节点级别的 nodeShape

在调用 setJsonData() 之前有两个很小的预处理步骤。代码会用 line-${index} 补齐缺失的连线 id,并将数值型的 nodeShape 转换为当前 API 所需的 RGNodeShape 枚举。完成这些规范化之后,整份数据只会加载一次,后续所有标签变更都通过定向的节点更新完成。

在真实产品中,同样的数据结构可以表示流程节点、设备标签、组织单元、平面图标注,或需要快速维护文本而不必打开独立表单的知识图谱实体。

relation-graph 的使用方式

入口组件用 RGProvider 包裹整个 demo,MyGraph 使用 RGHooks.useGraphInstance() 来初始化和变更图。主要选项设置了 fixed 布局、默认矩形节点、直线连线、边框连接点、蓝色连线颜色,以及线宽 2。由于布局是固定的,内联节点坐标决定了初始位置,而不是依赖运行时的树布局或力导布局。

自定义渲染路径以 RGSlotOnNode 为中心。每个渲染出来的节点都会被替换为 MyEditableNode,它接收运行时的 node、全局 isEditingEnabled 标志,以及一个通过 graphInstance.updateNode(...) 持久化文本变更的回调。当前源码没有使用连线插槽或活动视图插槽;这里值得关注的扩展点是节点插槽。

图实例 API 分两层使用。MyGraph 在挂载期间调用 setJsonData()moveToCenter()zoomToFit(),然后在确认标签编辑时调用 updateNode()DraggableWindow 内部共享的 CanvasSettingsPanel 使用 RGHooks.useGraphStore() 读取当前滚轮和拖拽行为,使用 setOptions() 在运行时切换这些行为,并通过 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 执行导出流程。

样式分布在图选项和 CSS 两部分。SCSS 文件通过画布偏移与缩放变量以及径向渐变的点状背景来自定义 .relation-graph,而可编辑节点组件则提供了黄色输入框,以及在可编辑时显示的 cursor-text 光标提示。

关键交互

双击是主要入口。只有当全局复选框启用了该模式时,节点才会进入内联编辑,并且处理函数会阻止事件冒泡,这样图就不会把同一次手势解释为普通的节点点击。

开始编辑后,输入框会自动获得焦点并选中文本。按 Enter 或使输入框失焦会提交变更,按 Escape 则会恢复之前的节点文本并退出而不保存。

编辑开关是全局的,而不是按节点单独控制。浮动窗口中的复选框会更新 MyGraph 中的 isEditingEnabled,这个布尔值随后会传递给每一个通过插槽渲染的节点。

浮动窗口还增加了次级控制。它可以被拖动、最小化、展开为设置面板,可用来在滚动、缩放和无之间切换滚轮行为,可用来在选择、移动和无之间切换画布拖拽行为,也可以将当前图导出为图片。

关键代码片段

这段代码展示了固定布局图的配置,以及自定义节点渲染器所依赖的默认可视规则。

const graphOptions: RGOptions = {
    defaultLineColor: '#43a2f1',
    defaultLineWidth: 2,
    layout: {
        layoutName: 'fixed'
    },
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardStraight,
    defaultJunctionPoint: RGJunctionPoint.border
};

这段代码说明示例会在加载数据前先对输入数据做规范化处理,然后再将固定布局图居中显示。

myJsonData.lines.forEach((line, index) => {
    if (!line.id) {
        line.id = `line-${index}`;
    }
});

myJsonData.nodes.forEach(node => {
    if (node.nodeShape === 0) node.nodeShape = RGNodeShape.circle;
    if (node.nodeShape === 1) node.nodeShape = RGNodeShape.rect;
});

await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

这段代码证明,relation-graph 的节点插槽正是将默认节点主体替换为可编辑渲染器的机制。

<RelationGraph options={graphOptions}>
    <RGSlotOnNode>
        {({ node, checked, dragging }: RGNodeSlotProps) => (
            <MyEditableNode
                node={node}
                enableEditingMode={isEditingEnabled}
                onNodeTextChange={onNodeTextChange}
            />
        )}
    </RGSlotOnNode>
</RelationGraph>

这段代码展示了自定义节点如何进入编辑模式,并立即为文本替换准备输入框。

useEffect(() => {
    if (isEditing && inputRef.current) {
        inputRef.current.focus();
        inputRef.current.select();
    }
}, [isEditing]);

const handleDoubleClick = (e: React.MouseEvent) => {
    if (enableEditingMode) {
        e.stopPropagation();
        setIsEditing(true);
    }
};

这段代码展示了提交与取消流程,包括最终通过 graphInstance.updateNode(...) 持久化文本的回调。

const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
        finishEditing();
    } else if (e.key === 'Escape') {
        setEditingText(node.text || '');
        setIsEditing(false);
    }
};

const finishEditing = () => {
    if (editingText !== node.text) {
        onNodeTextChange(node, editingText);
    }
    setIsEditing(false);
};

这段代码展示了共享设置面板如何通过当前图实例实时修改图行为。

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

这个示例的独特之处

根据已准备的文档上下文,这个示例的独特之处并不在于共享的浮动窗口或 provider 与 hook 的组合方式,因为附近的 deep-eachinvestmentselections 等示例也复用了这套脚手架。更少见的组合是自定义 MyEditableNode 渲染器,加上受模式控制的双击编辑、自动聚焦与文本全选、Enter 或失焦提交、Escape 取消,以及在固定布局图上通过 updateNode() 持久化修改。

稀有度记录还表明,黄色内联编辑器状态、带点状背景的编辑画布,以及固定位置的样式示例节点,在这组示例中都较为少见。这使得该 demo 相比那些强调选择、遍历、尺寸调整或更广泛编辑器行为的示例,更适合作为节点标签维护的直接起点。

它也比完整的图编辑器更聚焦。代码不会添加节点、重新连接连线,也不会管理多步骤编辑状态。它的价值在于隔离出一个具体能力:用内联编辑器替换默认节点内容,并让持久化路径保持简洁。

这种模式还适用于哪里

这种模式很适合用于结构已经明确,但标签需要在上下文中快速修正的图表。示例包括工作流命名工具、流程图、组织架构图、设备图、拓扑标签,以及知识图谱整理界面。

生产版本可以保留相同的基于插槽的编辑策略,同时将内联示例数据替换为服务器数据,在调用 updateNode() 之前加入校验规则、保存审计历史,或为节点文本之外的字段打开更丰富的侧边面板。核心思想仍然不变:让图始终保持已加载状态,只编辑那个发生变化的标签。