JavaScript is required

选中节点定向连线创建

这个示例展示一种聚焦的图谱创作模式:选中节点后显示方向芯片,用于创建新的出向连接。它利用 relation-graph 的编辑状态、节点周边覆盖层渲染和交互式连线创建流程,在不搭建完整编辑器壳层的前提下保存新边。

从选中节点创建定向连线

这个示例构建了什么

这个示例构建了一个轻量级图编辑工作区,其中一个被选中的节点会成为新建出向连接的起点。页面初始载入的是一个小型预置图,隐藏了内置工具栏,并额外添加了自定义的浮动辅助窗口和环绕节点的工具栏。

用户可以点击节点,使用修饰键调整编辑选区,拖动选择框,然后从选中节点上方、下方、左侧或右侧的小型操作按钮发起一条新连接。这个示例的重点并不是通用节点操作,而是展示一种聚焦模式:从附着在单个节点上的上下文控件中启动 relation-graph 的交互式连线创建流程。

数据是如何组织的

图数据以内联方式声明为一个 RGJsonData 对象,其中包含 rootId、扁平的 nodes 数组以及扁平的 lines 数组。每条连线只需要 idfromto,这让初始数据集保持小巧,也便于替换为业务实体,例如人员、服务、工作流步骤或文档依赖关系。

在调用 setJsonData(...) 之前没有预处理流程。初始化发生在挂载时的 effect 中:示例先创建 JSON 对象,将其加载到图实例中,然后调用 zoomToFit()。新连接也不会保存在独立的编辑器状态里;交互式拖拽完成后,如果目标暴露了 id,它们会直接通过 addLines(...) 追加到图中。

relation-graph 是如何使用的

入口组件通过 RGProvider 包裹整个页面,而 MyGraph 则通过 RGHooks.useGraphInstance() 读取 provider 作用域内的实例。这个实例负责初始数据加载、视口自适应、编辑状态切换、启动连线手势、生成 id、追加新连线、清除选中状态,以及从浮动设置面板更新运行时选项。

这个示例没有定义自定义布局对象。相反,它依赖运行时对预置图数据的默认处理,并在加载后立即将结果适配到当前视口。

RelationGraph 配置了 showToolBar: false,因此所有交互支撑都是自定义实现的。组件注册了节点点击、连线点击、画布点击以及选择框完成等处理函数。这些处理函数会在不同用户手势之间保持编辑状态同步。

这里最重要的 relation-graph 技术点是叠加层模式。RGSlotOnView 挂载了 RGEditingNodeController,而在这个控制器内部,自定义的 MyNodeToolbar 会读取 RGHooks.useEditingNodes()。由于这个工具栏会检查当前是否恰好只有一个编辑节点,因此这些快速创建按钮只会在存在单个有效源节点时出现。

工具栏操作并不会手动绘制连线。每个按钮都会把预设好的 JsonLineLike 模板传入 startCreatingLinePlot(...),其中包含颜色、宽度、线条形状以及 fromJunctionPoint。这样一来,relation-graph 会负责引导式拖拽连接交互,而示例代码只需控制连线从哪里开始以及它的外观。

浮动的 DraggableWindow 相比核心编排流程是次要部分,但它依然展示了另一个有用的集成点。它的设置面板通过 useGraphStore() 读取当前滚轮和拖拽模式,通过 setOptions(...) 更新这些模式,并使用 prepareForImageGeneration()restoreAfterImageGeneration() 将图导出为图片。

关键交互

  • 不带修饰键点击节点时,会用该节点替换当前编辑节点集合,并清除当前活动编辑连线。
  • 按住 ShiftCtrlMeta 点击节点时,会在编辑选区中切换该节点,而不是替换整个集合。
  • 拖动选择框结束后,会调用 getNodesInSelectionView(...),解析出的节点会成为新的编辑选区。
  • 点击空白画布会清除编辑节点、清除当前活动编辑连线,并移除选中状态。
  • 点击一条连线会将该连线标记为当前编辑连线,这让连线选中样式变得有意义,即使这个示例并没有暴露连线路径编辑控件。
  • 当且仅当选中了一个节点时,环绕节点的操作按钮才会出现,并以带方向预设的方式启动 startCreatingLinePlot(...)
  • 只有当拖拽在具有 id 的目标上结束时,新边才会被持久化;回调随后会生成新的连线 id,并通过 addLines(...) 追加该连线。
  • 浮动辅助窗口可以在运行时切换滚轮和拖拽行为,也可以把当前图视图导出为图片。

关键代码片段

下面这个片段展示了该示例如何使用内联的初始数据集,并在加载后立即让图适配视口。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        rootId: 'a',
        nodes: [
            { id: 'a', text: 'Border color' },
            // ...
        ],
        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]);
        }
    }
});

下面这个片段展示了环绕节点工具栏如何被限制为仅在单个编辑节点场景下生效,并注入源节点侧的连接点预设。

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

return onlyOneNodeBeSelected ? (
    <div
        style={{ backgroundColor: '#e85f84' }}
        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;
    }
}

这个示例的独特之处

它最主要的特点,是非常收敛地聚焦在上下文化边创建这一点上。与 line-vertex-on-node 相比,这个示例只关注源节点一侧:它会为选中节点提供带方向的预设,但不会额外增加用于选择目标连接点的界面。这让它在需求只是“选择一个节点,并从一个可预期的方向创建出向连接”时,成为一个更小更直接的参考。

custom-node-quick-actions 相比,这里的环绕节点控件并不是占位按钮或通用快捷方式。每个按钮都会启动真实的图变更流程,并通过 generateNewUUID(...)addLines(...) 持久化完成后的连接。这个工具栏也比许多叠加层示例更严格,因为只要不是恰好选中了一个节点,它就会消失。

editor-button-on-line 这类更完整的编辑器示例相比,这里的可复用价值在于克制。它结合了点阵编辑画布、浮动工具窗口、在节点、连线、画布和框选事件之间同步的编辑状态,以及单节点连线启动器,但并没有扩展成一个包含节点创建、尺寸调整手柄、自定义连线插槽或内联删除工具的完整图编辑器。

这种模式还适用于哪里

这种模式很适合工作流构建器,在这类场景中,每个步骤都需要从节点特定方向暴露受控的出向转换。

它同样适用于依赖关系映射工具、服务拓扑编辑器和知识图谱整理界面。在这些界面中,用户需要一个快速的“从当前选中项发起连接”操作,而不必打开独立的创建对话框。

在内部管理工具中,当连接样式应该被限制在少量预设内,而不是完全自由定义时,同样的方法也可用于组织架构图、审批流或数据血缘视图。