JavaScript is required

选中节点创建连线并选择目标端点位置

这个示例展示紧凑的图谱创作流程:选中节点后显示方向芯片以发起新出向连接。它把自定义节点周边工具栏与 relation-graph 内置连线覆盖层结合,使用户可先指定源侧,再选择目标节点上的连接端点位置后保存新线。

带目标端点选择的选中节点连线创建

这个示例构建了什么

这个示例在一个小型预置图之上构建了一个紧凑的连线编排工作区。页面使用全高的点状画布、一个浮动辅助窗口,以及一个自定义的环绕节点工具栏;该工具栏只会在恰好有一个节点处于编辑状态时出现。

用户可以点击节点、结合修饰键调整选择、清除画布状态,并从所选节点上方、下方、左侧或右侧的方向按钮发起一条新的出边。这个示例的核心在于,这个流程不会在选择目标节点时结束:示例还挂载了 relation-graph 内置的连线覆盖层,使用户可以进一步选择新连线落在目标节点的哪个位置。

数据如何组织

图数据直接在 initializeGraph() 内以内联方式组装为一个 RGJsonData 对象,其中包含一个 rootId、一个扁平的 nodes 数组,以及一个扁平的 lines 数组。这个预置数据集规模小且写死在代码中,因此很容易替换为工作流步骤、服务、审批项或依赖项等业务实体。

在调用 setJsonData(...) 之前,没有额外的预处理步骤,只有在代码中构建这个 JSON 对象。组件挂载时的 effect 会加载数据,并立即调用 zoomToFit()。新连接也不会存放在单独的草稿模型中;一旦创建连线的回调解析出合法的目标节点,示例就会通过 addLines(...) 直接追加这条新边。

relation-graph 的使用方式

入口组件用 RGProvider 包裹整个示例,MyGraph 则通过 RGHooks.useGraphInstance() 作为核心控制面。这个实例负责加载图、适配视口、更新编辑节点和编辑线状态、发起连线创建手势、生成 id、追加已确认的连线、清除选中状态,并支持浮动设置面板和导出工具。

这个示例没有定义自定义布局对象。它依赖 relation-graph 对这份内联数据集的常规处理,然后再通过 zoomToFit() 调整结果视图。

RelationGraph 配置了 showToolBar: false,因此页面用自己的编辑外壳替代了内置工具栏。组件注册了节点点击、连线点击、画布点击以及选框完成等处理器,而这些处理器都会汇入同一套编辑状态模型。

覆盖层结构是这个示例中 relation-graph 的关键模式。RGSlotOnView 挂载了 RGEditingNodeController 来承载自定义的 MyNodeToolbar,同时也挂载了 RGEditingConnectController,从而让运行时在同一次手势过程中暴露目标侧挂点选择能力。自定义工具栏使用 RGHooks.useEditingNodes() 来强制只在单节点时启用,然后把预设的 JsonLineLike 模板传给 startCreatingLinePlot(...),其中包含颜色、宽度、RGLineShape.StandardCurve 以及按方位区分的 fromJunctionPoint 值。

浮动 DraggableWindow 是共享脚手架,但它同样展示了有价值的集成点。它的设置面板通过 RGHooks.useGraphStore() 读取实时画布模式值,通过 setOptions(...) 应用模式变更,并结合 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 以及 modern-screenshot 辅助库导出图像。本地样式表则增加了点状工作区背景,并让被选中的线标签继承当前线条颜色。

关键交互

  • 无修饰键点击节点时,会将编辑节点集合替换为该节点,并清除当前激活的编辑线。
  • 按住 ShiftCtrl 或受支持的 Meta 行为点击节点时,会将该节点切换进或切换出编辑选择。
  • 当画布处于选择模式时,完成一次拖拽选框后,会解析该区域内的节点,并用它们替换当前编辑节点集合。
  • 点击空白画布会清除编辑节点、清除当前激活的编辑线,并重置选中状态。
  • 点击一条线会将该线标记为当前编辑线。
  • 当恰好选中一个节点时,该节点周围会出现小按钮,并以顶部、底部、左侧或右侧的源端预设发起 startCreatingLinePlot(...)
  • 连线创建流程只有在所选目标节点暴露出 id 时才会真正保存新边;否则回调会忽略本次结果。
  • 浮动辅助窗口可以拖动、最小化、展开为设置面板,并用于修改滚轮或拖拽行为,或下载图像。

关键代码片段

这段代码展示了内联预置数据集以及挂载时的加载路径。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        rootId: 'a',
        nodes: [
            { id: 'a', text: 'Border color' },
            { id: 'a1', text: 'No border' },
            // ...
        ],
        lines: [
            { id: 'l1', from: 'a', to: 'b' },
            // ...
        ]
    };
    await graphInstance.setJsonData(myJsonData);
    graphInstance.zoomToFit();
};

这段代码展示了基于点击的选择和基于框选的选择如何汇聚到同一个编辑节点模型。

const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
    if ($event.shiftKey || $event.ctrlKey || ($event.metaKey && !$event.altKey)) {
        graphInstance.toggleEditingNode(nodeObject);
    } else {
        graphInstance.setEditingNodes([nodeObject]);
    }
    graphInstance.setEditingLine(null);
};

const onCanvasSelectionEnd = (selectionView: RGSelectionView) => {
    const willSelectedNodes = graphInstance.getNodesInSelectionView(selectionView) || [];
    graphInstance.setEditingNodes(willSelectedNodes);
};

这段代码展示了示例如何把交互式拖拽委托给 relation-graph,并且只保存被接受的节点目标。

graphInstance.startCreatingLinePlot(e.nativeEvent, {
    template: lineTemplate,
    fromNode: fromNode,
    onCreateLine: (fromNode, toNode, newLineJson) => {
        if (toNode.id) {
            const newLineId = graphInstance.generateNewUUID(8);
            const newLineJsonData = Object.assign({}, newLineJson, {
                from: fromNode.id,
                to: toNode.id,
                text: 'New Line ' + newLineId
            });
            graphInstance.addLines([newLineJsonData]);
        }
    }
});

这段代码展示了该示例如何把自定义的环绕节点覆盖层与 relation-graph 内置的目标挂点覆盖层结合起来。

<RGSlotOnView>
    <RGEditingNodeController>
        <MyNodeToolbar onStartCreateLine={onStartCreateLine} />
    </RGEditingNodeController>
    {/* Node connection point selector component */}
    <RGEditingConnectController />
</RGSlotOnView>

这段代码展示了单节点限制,以及某个工具栏按钮注入的源端预设。

const editingNodes = RGHooks.useEditingNodes();
const onlyOneNodeBeSelected = editingNodes.nodes.length === 1;

return onlyOneNodeBeSelected ? (
    <div onClick={(e) => {
        onStartCreateLine(editingNodes.nodes[0], {
            lineWidth: 3,
            color: '#e85f84',
            fromJunctionPoint: RGJunctionPoint.top,
            lineShape: RGLineShape.StandardCurve,
            text: 'New Line'
        }, e);
    }}>T1</div>
) : null;

这段代码展示了样式表覆盖如何将画布变成类似编辑器的工作表面,并用线条颜色高亮被选中的线标签。

.relation-graph {
    --rg-canvas-scale: 1;
    --rg-canvas-offset-x: 0px;
    --rg-canvas-offset-y: 0px;
    background-position: var(--rg-canvas-offset-x) var(--rg-canvas-offset-y);
    background-size: calc(var(--rg-canvas-scale) * 15px) calc(var(--rg-canvas-scale) * 15px);
    background-image: radial-gradient(circle, rgb(197, 197, 197) calc(var(--rg-canvas-scale) * 1px), transparent 0);
}

.rg-line-peel.rg-line-checked .rg-line-label {
    background-color: var(--rg-line-color);
    color: #fff;
}

这个示例的独特之处

对比数据清楚地说明了它的主要差异:这不只是一个基于选中节点的快捷建线示例。与 create-line-from-node 相比,它保留了相同的上下文工具栏模式,但增加了 RGEditingConnectController,因此整个手势流程包含目标侧挂点选择,而不是停留在更简单的节点到节点连接上。

它与 change-line-verticeschange-line-path 这类邻近的连线编辑示例也不同。这些示例会在编辑已有边时使用连接覆盖层,而这个示例是在创建新边时使用该覆盖层。用户从一个已选中的节点出发,发起一条预设的出边,然后再决定这条新边应当如何落在目标节点上。

batch-operations-on-nodes 相比,这里的编辑节点覆盖层刻意更收敛。基于修饰键的多选和框选切换依然存在,但可见控件被刻意限制在单节点场景,这让该示例成为一个聚焦于连线编排的微型编辑器,而不是更宽泛的节点维护工具。

这种模式还适用于哪里

这种模式可以适配工作流构建器:每个步骤需要从节点的特定侧暴露被许可的出向迁移,同时目标挂点的几何位置还会影响可读性。

它也适用于服务拓扑编辑器、依赖关系图、审批流工具以及知识图谱整理界面,特别是在用户需要快速执行“从当前选中项发起连接”而不想打开单独表单的场景中。

同样的结构还可以扩展到需要受限图编排的内部工具中:保留上下文发起器,保留内置的目标挂点选择器,再把内联预置数据替换为来自后端或编辑器状态仓库的领域记录。