JavaScript is required

预设工具栏创建节点与连线

这个示例展示固定布局 relation-graph 编辑器:通过视图内挂载的预设面板拖拽创建样式节点、点击创建样式连线。它还演示了基于自定义卡片插槽的混合节点渲染、创建状态反馈、共享悬浮设置面板和图片导出辅助。

固定图形画布上的预设工具栏节点与连线创建

这个示例构建了什么

这个示例构建的是一个轻量级的图谱编辑画布,而不是只读图谱查看器。界面展示了一个位于固定位置的起始图谱、一个点状背景、一个挂载在图谱视图内部的橙色预设面板、一个共享的浮动帮助窗口,以及对纯文本节点和更丰富的卡片式节点的支持。

主要用户操作是结构性编辑。用户从左侧面板拖动节点预设到画布上以创建新节点,点击连线预设进入引导式连线模式,并通过选择现有节点来完成连线。其中一个预设会创建 myCard 节点,它通过自定义插槽渲染,而不是使用默认的文本节点主体。

最重要的设计思路是,节点创建和连线创建共享同一套视图内预设工作流。因此,这个示例是一个紧凑的参考案例,展示了如何用模板驱动的方式构建具有混合节点样式的图谱。

数据是如何组织的

初始图谱数据在 initializeGraph() 中以内联方式声明为一个 RGJsonData 对象,其中包含 rootId、扁平的 nodes 数组和扁平的 lines 数组。由于图谱使用固定布局,种子节点已经带有显式的 xy 坐标;各个节点还分别覆盖了边框、填充和字体设置,以展示不同的样式效果。

除了种子图谱之外,还有两层附加数据。nodeTemplates 保存了可复用的部分节点定义,包括矩形、圆形和 myCard 变体;lineTemplates 保存了可复用的部分连线定义,包括直线、曲线和正交连线。在加载起始图谱之前,会先规范化缺失的连线 id。在运行时创建新节点或新连线之前,会对选中的模板做深拷贝,从而避免实时图谱变更复用同一个对象引用。

运行时插入步骤还会做一些小规模预处理。新节点会从选中的模板中继承尺寸,当模板缺少尺寸时回退到 96x96,然后再调整最终的 xy,使拖放得到的节点以指针位置为中心。在真实产品中,同样的结构可以表示工作流步骤、设备符号、拓扑设备、组织角色,或者任何其他经过约束的图元素集合。

relation-graph 是如何使用的

入口文件用 RGProvider 包裹整个 demo,MyGraph 和工具栏都通过 RGHooks.useGraphInstance() 访问当前图实例。主图选项将 layout.layoutName 设为 fixed,默认节点形状保持为矩形,默认使用直线和边框连接点,设置了默认连线颜色和宽度,并将内置工具栏放在右侧,而自定义预设面板则单独挂载在左侧。

这个示例使用了两个插槽扩展点。RGSlotOnNode 根据节点类型切换渲染:普通节点显示简单标签,而 myCard 节点渲染 MyCardNodeContent,它内部又包装了共享的 Simple3DCard 视觉外壳。RGSlotOnViewDragToCreateToolbar 直接挂载到图谱视口内部,因此创建工具始终附着在画布上,而不是放在外部页面布局里。

图实例 API 同时驱动初始化和编辑。setJsonData()moveToCenter() 负责加载起始图谱,而 startCreatingNodePlot(...)startCreatingLinePlot(...)generateNewUUID()addNodes(...)addLines(...) 实现运行时编辑。工具栏还会在允许创建连线之前检查 getNodes(),因此连线工具会受到当前可见节点存在性的约束。

这里的 hooks 不仅用于访问图实例,也用于编辑器状态。useCreatingNode()useCreatingLine() 会把当前激活的创建模式反馈到预设芯片样式上,共享的浮动 DraggableWindow 则通过 useGraphStore() 配合 setOptions() 暴露滚轮与拖拽行为开关。同一个窗口还通过 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 执行导出流程。

样式分散在示例样式表和共享视觉辅助样式中。my-relation-graph.scss 创建了随缩放感知的点状画布,并用当前连线颜色填充被选中的连线标签;DragToCreateToolbar.scss 定义了半透明橙色侧边栏和激活芯片的光晕;Simple3DCard.scss 提供了辉光、倾斜和全息效果,这些样式同时复用于预设预览和最终渲染出的卡片节点。

关键交互

拖动节点预设是主要的创建流程。按下某个节点芯片会启动 startCreatingNodePlot(...),在画布上释放后就会插入一个新节点,其样式来自所选模板。

点击连线预设是第二种创建流程。工具栏会先检查是否至少存在一个可见节点;否则会显示错误消息,并拒绝进入连线模式。

当允许进入连线模式时,界面会显示一条成功提示,提示用户去点击节点。只有当最终目标能解析为节点 id 时,这条连线才会被真正持久化,因此指向空白区域的未完成连接会被忽略。

预设面板也会暴露模式反馈。当 relation-graph 报告节点创建或连线创建处于激活状态时,对应的芯片会获得激活样式类。

浮动帮助窗口增加了次级交互。它可以被拖动、最小化、切换到设置面板,用于把滚轮行为切换为滚动、缩放或禁用,用于把画布拖拽行为切换为框选、移动或禁用,还可以用于将当前图谱导出为图片。

关键代码片段

这个片段展示了,图谱被配置为一个固定布局的编辑表面,并具有显式的默认节点和连线行为。

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

这个片段展示了内联的种子图谱,以及在调用 setJsonData() 之前为其分配 id 的规范化步骤。

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'Border color', borderColor: '#43a2f1', x: -48, y: -48 },
        { id: 'a1', text: 'No border', borderWidth: -1, color: '#ff8c00', x: 271.2, y: -48 },
        { id: 'a2', text: 'Plain', borderWidth: 3, borderColor: '#ff8c00', fontColor: '#ff8c00', color: 'transparent', x: 71.15, y: 243.9 }
    ],
    lines: [{ from: 'a', to: 'a1' }, { from: 'a', to: 'a2' }]
};

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

这个片段证明了节点创建是模板驱动的,并且新节点会以释放点为中心放置。

graphInstance.startCreatingNodePlot(event, {
    templateText: tempNode.text,
    templateNode: JSON.parse(JSON.stringify(tempNode)),
    onCreateNode: (x: number, y: number) => {
        const nodeSize = { width: (tempNode.width || 96), height: (tempNode.height || 96) };
        const newId = graphInstance.generateNewUUID();
        graphInstance.addNodes([Object.assign({}, tempNode, {
            id: 'newNode-' + newId,
            text: 'New node' + newId,
            x: x - (nodeSize.width / 2),
            y: y - (nodeSize.height / 2)
        })]);
    }
});

这个片段展示了,在将新边追加到图谱之前,引导式连线创建会先经过校验。

if (!graphInstance.getNodes().some((node: RGNode) => !node.opacity || node.opacity > 0)) {
    return SimpleGlobalMessage.showMessage({ type: 'error', message: 'Please create the node first!' });
}

graphInstance.startCreatingLinePlot(event, {
    template: JSON.parse(JSON.stringify(template)),
    onCreateLine: (from: RGNode, to: RGNode | RGPosition, finalTemplate: JsonLine) => {
        if (to.id) {
            const newLineId = graphInstance.generateNewUUID();
            graphInstance.addLines([Object.assign({}, finalTemplate, {
                from: from.id,
                to: to.id
            })]);
        }
    }
});

这个片段展示了,这个示例如何在同一个 RelationGraph 中结合自定义节点插槽和挂载在视图内的工具栏。

<RelationGraph options={graphOptions}>
    <RGSlotOnNode>
        {({ node }: RGNodeSlotProps) => {
            if (node.type === 'myCard') {
                return <MyCardNodeContent name={node.text} />;
            }
            return <div className="rg-node-text">{node.text}</div>;
        }}
    </RGSlotOnNode>
    <RGSlotOnView>
        <DragToCreateToolbar />
    </RGSlotOnView>
</RelationGraph>

这个片段展示了,浮动设置面板如何通过图实例修改实时画布行为,并执行图片导出流程。

const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
    graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();

这个示例的独特之处

根据对比数据,最具辨识度的部分是同一个预设面板在图谱视图内部同时处理两种编辑模式。用户从同一个左侧工具栏拖动带样式的节点芯片来创建节点,也从这里点击带样式的连线芯片来启动引导式边创建。附近的一些编辑示例同样会在运行时修改图谱,但不会像这个示例一样强调这种紧凑的、由预设面板驱动的工作流。

amount-summarizer 相比,这个示例更聚焦,也更通用。两个示例都使用预设驱动创建,但 amount-summarizer 叠加了重新计算、删除流程和重新布局等编辑逻辑,而这个示例则始终聚焦于带样式的创建模式和混合节点渲染。

create-object-from-menu 相比,这里可复用的经验是一个持续存在于画布内的预设面板,而不是上下文菜单。与 freely-draw-lines-on-canvasedit-node-text 相比,这里的重点是可复用样式预设和运行时图谱增长,而不是自由绘制几何线条,或在静态图谱上做内联标签编辑。

另一个独特的组合来自视觉和插槽用法:固定的点状画布、橙色预设侧边栏、通过 useCreatingNode()useCreatingLine() 实现的激活模式芯片高亮、纯文本节点与 myCard 渲染器混用,以及在预设预览和最终节点输出之间共享的 3D 卡片样式。

这个模式还能应用到哪里

这种模式很适合迁移到工作流构建器、系统架构编辑器、拓扑草图工具、流程设计器,以及任何其他需要用户从受控的已批准节点和边样式库中组合图谱,而不是任意创建图形的工具。

在生产版本中,可以保留同样的预设表和基于插槽的渲染方式,同时将内联示例数据替换为服务端数据、给每个模板附加业务元数据、在 addLines(...) 之前强制执行连接规则,或增加持久化与撤销历史。其核心可复用思路保持不变:通过把经过整理的模板转化为受引导的 relation-graph 创建流程,让图谱编辑保持轻量。