JavaScript is required

画布框选与节点创建

这个示例构建了一个全屏的 relation-graph 工作区,并预置了 14 个彼此断开的字母节点。一个可拖拽的辅助窗口始终位于画布上方,用来说明如何开始框选、显示实时的选择框尺寸信息,并提供可切换项来改变拖拽手势的行为。

用于拾取与图形创建的画布框选

这个示例构建了什么

这个示例构建了一个全屏的 relation-graph 工作区,并预置了 14 个彼此断开的字母节点。一个可拖拽的辅助窗口始终位于画布上方,用来说明如何开始框选、显示实时的选择框尺寸信息,并提供可切换项来改变拖拽手势的行为。

其核心行为是,同一次框选手势会产生两种可能结果。在普通模式下,它会选中拖拽区域内的所有节点。在创建模式下,它会把同一区域转换成一个新的圆形或矩形节点,节点尺寸来自拖出的框选区域。因此,这个示例更像是一个围绕选择驱动工具的紧凑参考,而不是某个特定领域的图表。

数据是如何组织的

初始数据集是在 initializeGraph 内部创建的一个内联 RGJsonData 对象。它包含一个扁平的 nodes 数组,节点 id 从 an,以及一个空的 lines 数组,因此起点被刻意设计得简单且没有连线关系。

这里没有外部获取数据,也没有在调用 setJsonData(...) 之前做任何预处理,除了构造这个本地对象。数据加载完成后,示例会将图谱居中并缩放到适合当前视口。在交互过程中,选择框会成为临时的运行时输入:它要么被传给 getNodesInSelectionView(...) 用于多选,要么会被转换为画布坐标,并通过 addNode(...) 用来确定新节点的尺寸。

在生产应用中,同样的数据结构可以表示规划看板上的卡片、拓扑画布上的设备、平面图中的座位,或者标注工作区中的资源,此时区域选择与基于区域的创建比线关系本身更重要。

relation-graph 是如何使用的

这个示例外层使用 RGProvider 包裹,然后 MyGraph 通过 RGHooks.useGraphInstance() 来加载数据、居中视图、清理状态、解析选择区域内的节点,以及插入新节点。它还使用 RGHooks.useSelection(),让当前选择框既能驱动浮动状态卡片,也能驱动临时图形预览。

图谱选项不多,但都经过了明确设计:showToolBar 用于启用内置工具栏,checkedItemBackgroundColor 为选中项提供半透明绿色背景,defaultLineWidth 保持最小值,而 dragEventAction 绑定到 React 状态,因此画布可以在运行时于 selectionmove 之间切换。本地代码没有声明自定义布局;它只是加载示例数据,然后通过 moveToCenter()zoomToFit() 来规范化视口。

RelationGraph 接收关键事件处理器,尤其是 onCanvasClickonCanvasSelectionEndRGSlotOnNode 用一个带内边距的文本渲染器覆盖了节点内容。浮动的 DraggableWindow 组件提供说明面板,并打开一个共享设置面板;这个面板通过 RGHooks.useGraphStore() 读取当前选项,使用 setOptions(...) 更新滚轮和拖拽行为,并通过 prepareForImageGeneration() 配合一个 DOM-to-image 辅助方法导出图谱图片。SCSS 文件只定义了占位选择器,因此大部分可见定制都来自 JSX、类似 Tailwind 的工具类,以及 MyCreatingShapeLayer 中的 SVG 覆盖层。

关键交互

  • 按住 Shift 开始画布框选,或者在辅助面板中显式将画布拖拽行为切换为 selection 模式。
  • 在普通模式下拖出选择框,查找区域内的节点并将它们标记为选中。
  • 启用圆形或矩形创建后,拖出同样的选择框,即可插入一个新节点,其尺寸来自拖拽边界。
  • 点击画布空白处以清除图谱中的 checked 和 selected 状态。
  • 打开浮动设置面板以修改滚轮行为、修改拖拽行为,或将当前图谱下载为图片。

节点点击和连线点击处理器也存在,但在这个示例中它们只会把对象打印到控制台,不会改变图谱状态。

关键代码片段

图谱实例、实时选择状态和运行时拖拽模式在 MyGraph 顶部被组织在一起。

const graphInstance = RGHooks.useGraphInstance();
const selectionView = RGHooks.useSelection();
const [dragMode, setDragMode] = React.useState<'move'|'selection'>('selection');
const [selectionForCreateNode, setSelectionForCreateNode] = React.useState<''|'circle'|'rect'>('');

const graphOptions: RGOptions = {
    debug: false,
    showToolBar: true,
    checkedItemBackgroundColor: 'rgba(0, 128, 0, 0.2)',
    defaultLineWidth: 1,
    dragEventAction: dragMode,
    defaultJunctionPoint: RGJunctionPoint.border
};

初始图谱数据是本地的、轻量的,并在视图居中和适配之前直接加载到 relation-graph 实例中。

const myJsonData: RGJsonData = {
    nodes: [
        { id: 'a', text: 'A' },
        { id: 'b', text: 'B' },
        // ...
    ],
    lines: []
};

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

当启用了某种创建模式时,onCanvasSelectionEnd 会把选择框转换为一个新节点。

if (selectionForCreateNode === 'circle') {
    const xyOnCanvas = graphInstance.getCanvasXyByViewXy(userSelectionView);
    graphInstance.addNode({
        id: graphInstance.generateNewNodeId(),
        nodeShape: RGNodeShape.circle,
        width: userSelectionView.width,
        height: userSelectionView.height,
        x: xyOnCanvas.x,
        y: xyOnCanvas.y
    });
}

非创建分支会使用 relation-graph 内置的选择查询来解析已有节点,并将它们标记为选中。

graphInstance.clearChecked();
graphInstance.clearSelected();
const nodesInSelection = graphInstance.getNodesInSelectionView(userSelectionView);
SimpleGlobalMessage.success(`Select [${nodesInSelection.length}] Nodes`);
nodesInSelection.forEach(node => {
   graphInstance.updateNode(node, { selected: true })
});

临时覆盖层会对负方向拖拽进行归一化,因此即使用户向左或向上拖动,预览仍能正确渲染。

const rectX = width > 0 ? x : x + width;
const rectY = height > 0 ? y : y + height;
const absWidth = Math.abs(width);
const absHeight = Math.abs(height);

{shape === 'rect' ? (
    <rect
        x={rectX}
        y={rectY}
        width={absWidth}
        height={absHeight}
        strokeDasharray="4 2"
    />
) : (
    <circle
        cx={rectX + absWidth / 2}
        cy={rectY + absHeight / 2}
        r={Math.min(absWidth, absHeight) / 2}
    />
)}

这个示例的独特之处

selections 相比,这个示例并不把框选本身视为最终结果。拖拽出的区域仍然可以选中已有节点,但它也可以成为新的图谱内容。这使得该示例从纯粹的拾取转向了混合式的选择与创作。

canvas-selection-pro 相比,这里的重点是显式教学,而不是优先追求快捷操作。辅助窗口始终展示移动或选择模式,以及圆形或矩形创建选项,因此整个工作流更容易被学习和改造。

对比数据也体现出一种不同的特性组合:手动画布模式切换、点击清空工作区、实时选择信息展示、虚线创建预览,以及由选择驱动的节点插入,全都集中在一个紧凑的界面中。其他相邻示例也覆盖了这一模式的部分内容,但如果你希望 relation-graph 原生的选择几何成为核心交互原语,这个示例尤其值得参考。

这一模式还适用于哪里

这种模式很适合轻量级白板编辑器,在这里,拖拽出的区域既可以选择现有对象,也可以创建一个新的有边界图形。它也适用于规划看板、平面图工具、拓扑草图工具和标注画布,在这些场景里,同一个手势既要支持基于区域的拾取,也要支持基于区域的插入。

另一条扩展路径是把选择框视为一种通用输入原语。它不一定只用于创建圆形和矩形节点;一旦用户选择了某种创建模式,同样的交接过程也可以用来创建分组卡片、有边界的容器、占位设备,或者特定领域的区域。