[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:selection-create-or-select-nodes":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 通过拖拽框选来创建或选择节点\n\n## 这个示例构建了什么\n这个示例构建了一个全高度的 relation-graph 工作区，初始放置了 14 个彼此独立的字母节点，并叠加了两个编辑控件：一个可拖拽的辅助窗口和一个位于顶部中间的紧凑形状面板。用户无需离开画布，就可以查看当前选择框、切换画布行为并导出图片。\n\n这个示例的核心结果是一个双用途的拖拽手势。当未启用创建模式时，拖出选择框会选中区域内已有的节点。当启用了圆形或矩形模式时，同样的手势会变成一次性节点创建操作，拖拽过程中会显示虚线预览覆盖层，并在手势完成时弹出提示消息。\n\n## 数据是如何组织的\n初始图数据在 `initializeGraph()` 中以内联方式组装为一个本地 `RGJsonData` 对象。它包含一个扁平的 `nodes` 数组，节点 id 从 `a` 到 `n`，以及一个空的 `lines` 数组，因此这个示例从一个中性的画布开始，而不是从某个特定领域的图开始。\n\n在调用 `setJsonData(...)` 之前，没有任何 fetch 步骤，也没有任何预处理，只有在内存中构造这个对象。数据加载完成后，示例会通过 `moveToCenter()` 和 `zoomToFit()` 将视口居中并适配到合适大小。\n\n在交互过程中，选择框会成为临时输入数据。在创建模式下，代码会把选择视图重新转换为画布坐标，并使用选择框的宽高来决定新节点的尺寸。在普通模式下，同一个选择视图会被传给 `getNodesInSelectionView(...)`，返回的每个节点都会被更新为 `selected: true`。\n\n在真实产品中，这种数据结构同样可以表示白板上的便签、拓扑草图中的设备、平面图编辑器中的房间，或标注画布上的通用资源。\n\n## relation-graph 是如何使用的\n这个 demo 由 `RGProvider` 包裹，`MyGraph` 依赖 `RGHooks.useGraphInstance()` 来完成初始数据加载、选择处理、节点插入以及运行时选项更新。它还使用 `RGHooks.useSelection()`，使当前选择框既能驱动浮动读数，也能驱动虚线预览层。在共享设置面板中，`RGHooks.useGraphStore()` 用来读取当前启用的拖拽模式和滚轮模式。\n\n图配置保持精简但有明确目的：`showToolBar` 保持内置工具栏可见，`checkedItemBackgroundColor` 为已勾选项提供半透明绿色高亮，`defaultLineWidth` 保持为 `1`，`defaultJunctionPoint` 则设置为 `RGJunctionPoint.border`。这个示例没有定义自定义布局；它加载的是简单的离散数据，并在挂载后规范化视口。\n\n`RelationGraph` 是事件枢纽。`onCanvasClick` 会清除 checked 和 selected 状态，`onCanvasSelectionEnd` 会在节点创建和节点选择之间分支处理，`onKeyboardUp` 则会为 `Q` 和 `W` 启用临时创建模式。`RGSlotOnNode` 用一个居中文本容器替换了默认节点主体。`MyCreatingShapeLayer` 添加了一个带有 `pointerEvents: 'none'` 的 SVG 覆盖层，因此预览图形不会阻挡画布交互。\n\n外围编辑器外壳由本地辅助组件构建，而不是通过额外的 graph slot 实现。`DraggableWindow` 提供了浮动说明面板，并且可以打开一个共享设置视图，用于修改 `wheelEventAction`、修改 `dragEventAction`，以及通过 `prepareForImageGeneration()`、`domToImageByModernScreenshot(...)` 和 `restoreAfterImageGeneration()` 导出当前图像。\n\n## 关键交互\n- 点击顶部中间面板中的圆形或矩形按钮，会启用一个临时创建模式，并将画布拖拽行为切换为 `selection`。\n- `RelationGraph` 上的 `onKeyboardUp` 也会通过 `KeyQ` 和 `KeyW` 启用创建模式，因此当图接收到键盘事件时，用户可以通过快捷键走同一套流程。\n- 当创建模式处于激活状态时，拖出选择框会显示虚线预览覆盖层，并在拖拽结束时创建一个新的圆形或矩形节点。\n- 当未启用任何创建模式时，拖出同样的选择框会解析出被包围的节点，显示数量提示，并将这些节点标记为已选中。\n- 点击画布空白区域会清除 checked 和 selected 状态。\n- 打开可拖拽辅助窗口中的设置面板后，用户可以修改滚轮行为、修改画布拖拽行为，并下载当前图的图片。\n\n节点点击和连线点击处理器是存在的，但在这个示例里，它们只会记录被点击的对象，不会改变可见行为。\n\n## 关键代码片段\n该组件通过 hook 和 React state 保存图实例、实时选择框以及当前创建模式。\n\n```tsx\nconst graphInstance = RGHooks.useGraphInstance();\nconst selectionView = RGHooks.useSelection();\nconst [selectionForCreateNode, setSelectionForCreateNode] = React.useState\u003C'' | 'circle' | 'rect'>('');\n\nconst graphOptions: RGOptions = {\n    debug: false,\n    showToolBar: true,\n    checkedItemBackgroundColor: 'rgba(0, 128, 0, 0.2)',\n    defaultLineWidth: 1,\n    defaultJunctionPoint: RGJunctionPoint.border\n};\n```\n\n这个示例有意从一组较小的内联数据集开始，并在加载后立即规范化视口。\n\n```tsx\nconst myJsonData: RGJsonData = {\n    nodes: [\n        {id: 'a', text: 'A'},\n        {id: 'b', text: 'B'},\n        {id: 'c', text: 'C'},\n        // ...\n    ],\n    lines: []\n};\n\nawait graphInstance.setJsonData(myJsonData);\ngraphInstance.moveToCenter();\ngraphInstance.zoomToFit();\n```\n\n当启用了创建模式时，`onCanvasSelectionEnd` 会把拖出的区域转换成一个新节点。\n\n```tsx\nif (selectionForCreateNode === 'circle') {\n    const xyOnCanvas = graphInstance.getCanvasXyByViewXy(userSelectionView);\n    graphInstance.addNode({\n        id: graphInstance.generateNewNodeId(),\n        nodeShape: RGNodeShape.circle,\n        width: userSelectionView.width,\n        height: userSelectionView.height,\n        x: xyOnCanvas.x,\n        y: xyOnCanvas.y\n    });\n}\n```\n\n非创建分支会把同一个选择框作为选择查询来使用，然后把画布恢复为普通拖拽状态。\n\n```tsx\ngraphInstance.clearChecked();\ngraphInstance.clearSelected();\nconst nodesInSelection = graphInstance.getNodesInSelectionView(userSelectionView);\nSimpleGlobalMessage.success(`Select [${nodesInSelection.length}] Nodes`);\nnodesInSelection.forEach(node => {\n    graphInstance.updateNode(node, {selected: true})\n});\n\ngraphInstance.updateOptions({\n    dragEventAction: 'move'\n});\nsetSelectionForCreateNode('');\n```\n\n一个小型辅助函数会让创建模式保持为瞬时状态，只有当用户显式启用某个形状时，才将画布切换到选择模式。\n\n```tsx\nconst onKeyboardUp = (e: KeyboardEvent) => {\n    if (e.code === 'KeyQ') {\n        setSelectionForCreateNodeMode('circle');\n    } else if (e.code === 'KeyW') {\n        setSelectionForCreateNodeMode('rect');\n    }\n};\n\nconst setSelectionForCreateNodeMode = (nodeShape: 'circle' | 'rect') => {\n    graphInstance.updateOptions({ dragEventAction: 'selection' });\n    setSelectionForCreateNode(nodeShape);\n};\n```\n\n预览覆盖层会为渲染统一负方向拖拽的情况，并保持对指针事件透明。\n\n```tsx\nconst rectX = width > 0 ? x : x + width;\nconst rectY = height > 0 ? y : y + height;\nconst absWidth = Math.abs(width);\nconst absHeight = Math.abs(height);\n\n\u003Csvg\n    style={{\n        position: 'absolute',\n        width: '100%',\n        height: '100%',\n        pointerEvents: 'none',\n    }}\n>\n```\n\n共享的浮动设置面板复用了 relation-graph API 来暴露运行时画布控制和图像导出能力。\n\n```tsx\nconst canvasDom = await graphInstance.prepareForImageGeneration();\nlet graphBackgroundColor = graphInstance.getOptions().backgroundColor;\nif (!graphBackgroundColor || graphBackgroundColor === 'transparent') {\n    graphBackgroundColor = '#ffffff';\n}\nconst imageBlob = await domToImageByModernScreenshot(canvasDom, {\n    backgroundColor: graphBackgroundColor\n});\nawait graphInstance.restoreAfterImageGeneration();\n```\n\n## 这个示例的独特之处\n相较于 [`canvas-selection`](./canvas-selection)，这个示例更强调瞬时编辑，而不是持久的模式切换。比较数据表明，`canvas-selection-pro` 增加了 `onKeyboardUp` 快捷键和一个专门的顶部中间面板，并在每次手势完成后清除当前激活的形状，同时将 `dragEventAction` 恢复为 `move`。这让创建行为更像一次性命令，而不是一种持续粘滞的画布状态。\n\n相较于 [`selections`](./selections)，这里的拖拽框不仅仅是一个选择工具。该示例会把选择几何重新转换为画布坐标，并用它来决定新节点的尺寸和位置，因此同一个手势可以根据当前启用的模式，选择已有内容或创建新内容。\n\n相较于 [`custom-node-quick-actions`](./custom-node-quick-actions) 和 [`create-line-from-node`](./create-line-from-node)，这里真正的编辑工具就是选择框本身。那些示例使用选择操作来决定哪些现有节点显示上下文控件，而这个示例则直接把拖出的区域作为创建几何。\n\n稀有度数据同样表明，这个示例组合了一组在整个示例集中并不常见的能力：`RGHooks.useSelection()`、虚线形状预览覆盖层、键盘与面板模式启用、在选择结束时创建节点、在选择结束时选择节点，以及在同一个轻量工作区中提供共享设置或导出面板。\n\n## 这种模式还能应用到哪里\n这种模式非常适合轻量级图表编辑器，用户需要在选择和有边界形状创建之间快速切换，而不必停留在永久工具模式中。它适用于白板、流程草绘工具、拓扑草图以及平面图工具，在这些场景中，拖出的区域既应定义位置，也应定义尺寸。\n\n它也适用于以标注为主的产品。评审工具、地图标记工具或设计反馈界面都可以复用这套流程，用一次拖拽手势来创建标注区域、占位区域、分组容器，或特定领域中的有边界对象。\n",false,500,1782615424650]