G6 框选交互 BrushSelect 完整指南:配置参数、选择模式与源码实现解析
G6 框选交互 BrushSelect 完整指南:配置参数、选择模式与源码实现解析
本文是 AntV G6 图可视化框架中框选(Brush Select)交互行为的实战指南。框选用于在画布上通过鼠标拖拽矩形区域,快速批量选中节点、边和组合(combo)元素,广泛应用于图谱编辑、批量操作、数据清洗等场景。读完本文,你将掌握 brush-select 的全部配置项(触发按键、可选元素、四种选择模式、即时框选、自定义框样式与选中状态),并能结合源码理解框选命中判定、状态合并与动画切换的底层原理,可在真实项目中直接复制运行。
一、功能概述与应用场景
框选交互(brush-select)允许用户按住快捷键并拖拽鼠标,在画布上拉出一个矩形选区,框选范围内的元素会被批量选中(切换为指定状态)。它天然适合:
- 快速选中一批元素:如同时高亮某子图的所有节点;
- 快速取消选中一批元素:配合
diff等选择模式实现状态翻转。
在 G6 中,brush-select 是内置行为,注册于 packages/g6/src/behaviors/index.ts,通过 type: 'brush-select' 即可直接使用,无需额外注册。
二、基本用法:两种声明方式
1. 快速配置(静态)
使用字符串形式直接声明,简洁但仅支持默认配置,且配置后不可动态修改:
const graph = new Graph({
// 其他配置...
behaviors: ['brush-select'],
});
2. 对象配置(推荐)
使用对象形式进行配置,支持自定义参数,且可以在运行时通过 graph.updateBehavior() 动态更新配置:
const graph = new Graph({
// 其他配置...
behaviors: [
{
type: 'brush-select',
key: 'brush-select-1',
immediately: true, // 可以看到框框笼罩过去时,元素立即被框选了
trigger: ['shift', 'alt', 'control'], // 配合多种按键进行框选
},
],
});
key 是行为的唯一标识(见 packages/g6/src/spec/behavior.ts),用于在运行时通过 graph.updateBehavior({ key: 'brush-select', ... }) 或 graph.setBehaviors([...]) 精确更新该行为。
三、完整配置项一览
| 配置项 | 说明 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 交互类型名称,内置为 brush-select |
brush-select | string |
brush-select |
✓ |
| animation | 是否启用选中状态切换动画 | boolean | false |
|
| enable | 是否启用框选功能,可传入函数按事件动态判断 | boolean | ((event: Event) => boolean) | true |
|
| enableElements | 可框选的元素类型 | (node | edge | combo)[] |
['node', 'combo', 'edge'] |
|
| immediately | 是否及时框选,仅在 mode 为 default 时生效 |
boolean | false |
|
| mode | 框选的选择模式 | union | intersect | diff | default |
default |
|
| onSelect | 框选元素状态回调 | (states: Record<string, string | string[]>) => void | ||
| state | 被选中时切换到该状态 | string(如 selected、active、inactive、disabled、highlight 或自定义状态) |
selected |
|
| style | 框选矩形的样式(见下文 style 表) | RectStyleProps | 见下文 | |
| trigger | 按下该快捷键配合鼠标拖拽进行框选;设为空数组 [] 表示直接拖拽框选;⚠️ 设为 ['drag'] 时会导致 drag-canvas 行为失效,两者不可同时配置 |
string[] | ['shift'] |
以上默认值与类型定义可在 packages/g6/src/behaviors/brush-select.ts 的 BrushSelectOptions 接口与 defaultOptions 中逐一核对。
四、交互式 Demo 与动态调试面板
官方 API 文档(packages/site/common/api/behaviors/brush-select.md)提供了一个可直接运行的交互示例:构建一个含 3 个节点、3 条边的图,开启 brush-select 行为,并通过 GUI 面板实时调节所有配置项:
createGraph(
{
data: {
nodes: [
{ id: 'node-1', style: { x: 200, y: 100 } },
{ id: 'node-2', style: { x: 360, y: 100 } },
{ id: 'node-3', style: { x: 280, y: 220 } },
],
edges: [
{ source: 'node-1', target: 'node-2' },
{ source: 'node-1', target: 'node-3' },
{ source: 'node-2', target: 'node-3' },
],
},
node: {
style: { fill: '#7e3feb' },
state: {
custom: { fill: '#ffa940' },
},
},
edge: {
stroke: '#8b9baf',
state: {
custom: { stroke: '#ffa940' },
},
},
behaviors: [
{
type: 'brush-select',
key: 'brush-select',
},
],
plugins: [{ type: 'grid-line', size: 30 }],
animation: true,
},
{ width: 600, height: 300 },
(gui, graph) => {
const options = {
key: 'brush-select',
type: 'brush-select',
animation: false,
enable: true,
enableElements: ['node', 'edge', 'combo'],
immediately: false,
mode: 'default',
state: 'selected',
trigger: 'shift+drag',
};
const optionFolder = gui.addFolder('BrushSelect Options');
optionFolder.add(options, 'type').disable(true);
optionFolder.add(options, 'animation');
optionFolder.add(options, 'enable');
optionFolder.add(options, 'enableElements', [
['node', 'edge', 'combo'],
['node', 'edge'],
['node', 'combo'],
['combo', 'edge'],
['node'],
['edge'],
['combo'],
]);
optionFolder.add(options, 'trigger', {
'shift+drag': ['shift'],
drag: [],
});
optionFolder.add(options, 'state', ['active', 'selected', 'custom']);
optionFolder.add(options, 'mode', ['union', 'intersect', 'diff', 'default']).onChange((e) => {
immediately.show(e === 'default');
});
const immediately = optionFolder.add(options, 'immediately');
optionFolder.onChange(({ property, value }) => {
graph.updateBehavior({
key: 'brush-select',
[property]: value,
});
graph.render();
});
},
);
该示例演示了两个关键工程实践:
- 动态更新:面板每次改动都会调用
graph.updateBehavior({ key: 'brush-select', <a href="https://link.gitcode.com/i/a8e6e58ef62f6904d1d4aced8d2fbf69" target="_blank">property]: value })并重新render()。源码中 [update() 会先unbindEvents()、deepMix合并新配置、再bindEvents()重新绑定事件,从而实现热更新; immediately与mode联动:只有mode === 'default'时immediately才生效,因此面板在切换 mode 时控制immediately控件的显隐。
五、核心配置项深度解析
5.1 trigger:触发方式
trigger 为 string[],表示需要按住哪些按键再拖拽鼠标触发框选,按键名遵循 MDN Key Values 规范(如 'Shift'、'Alt'、'Control'、'Meta')。默认值为 ['shift']。
- 空数组
[]:不需要配合任何按键,直接鼠标拖拽即可框选; - ⚠️ 重要冲突:
trigger设置为['drag']时会导致drag-canvas行为失效,两者不可同时配置,因为二者会争夺同一次指针拖拽事件。
实现上,brush-select 借助 packages/g6/src/utils/shortcut.ts 的 Shortcut 工具类监听键盘事件。isKeydown() 会过滤掉 'drag' 后通过 shortcut.match() 校验当前按下的按键组合是否与 trigger 一致;该工具类在 window 层面监听修饰键(Control、Alt、Meta、Shift),并在窗口重新获得焦点时清空按键记录,避免按键状态异常。
5.2 enableElements:可框选的元素类型
enableElements 控制哪些类型的元素参与框选,可取 'node'、'edge'、'combo' 的任意组合,默认全选。在 selector() 中会按类型遍历图数据(graphData[${itemType}s]),并跳过 getElementVisibility(id) === 'hidden' 的隐藏元素。
命中判定规则(见 selector() 注释):
- 节点:节点的包围盒中心(
graph.getElementPosition(id))位于矩形内时被选中; - Combo:combo 的包围盒中心位于矩形内时被选中;
- 边:特殊规则——当边的两端节点(
source和target)都已在框选范围内时,该边才会被额外选中(见源码 L306-L315 的补充逻辑)。
5.3 mode:四种选择模式
mode 决定框选结果与元素当前状态如何合并,源码 updateElementsStates() 中的 switch 分支给出了精确定义:
| 模式 | 行为 | 源码逻辑 |
|---|---|---|
union |
保持已选元素的当前状态,并追加指定的 state | states[id] = [...graph.getElementState(id), state] |
intersect |
已选元素若已有指定 state 则保留,否则清除该状态 | states[id] = prevStates.includes(state) ? [state] : [] |
diff |
对已选元素的指定 state 进行取反(有则移除,无则添加) | includes(state) ? filter 掉 : 追加 |
default |
清除已选元素的当前状态,仅保留指定的 state | states[id] = [state] |
默认值为 default。典型场景:连续框选多批区域时,union 可累加选中,diff 可把已选中的区域框选一次即取消选中,实现"快速取消选中一批元素"。
5.4 immediately:即时框选
immediately 为 true 时,鼠标拖拽过程中选区矩形一出现即实时更新选中状态;为 false(默认)时,仅在松开鼠标(pointerup)时一次性结算选中结果。注意:仅当 mode === 'default' 时该配置才生效(源码 onPointerMove() 中的判断 if (immediately && mode === 'default'))。
const graph = new Graph({
behaviors: [
{
type: 'brush-select',
key: 'brush-select',
immediately: true, // 框框笼罩过去时元素立即被选中
trigger: [], // 不需要配合其他按键,点击鼠标拖动即可框选
},
],
});
5.5 state:选中状态
state 指定元素被框选后切换到的状态名,默认 'selected'。你可以使用 G6 内置状态(selected、active、inactive、disabled、highlight),也可以使用图配置中 node.state / edge.state 里自定义的任何状态(如上文 Demo 中的 custom: { fill: '#ffa940' })。框选本质上就是调用 graph.setElementState(states, animation) 批量设置状态。
5.6 style:框选矩形样式
style 直接透传给 @antv/g 的 Rect 图形(源码 new Rect({ id: 'g6-brush-select', style })),支持以下属性:
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| cursor | 鼠标样式 | string | |
| fill | 填充颜色 | string | Pattern | null | #1677FF |
| fillOpacity | 填充透明度 | number | string | 0.1 |
| isBillboard | 是否启用公告牌模式 | boolean | |
| isSizeAttenuation | 是否启用大小衰减 | boolean | |
| lineCap | 线段端点样式 | butt | round | square |
|
| lineDash | 虚线配置 | number | string | (string | number)[] | |
| lineDashOffset | 虚线偏移量 | number | |
| lineJoin | 线段连接处样式 | miter | round | bevel |
|
| lineWidth | 线宽度 | number | string | 1 |
| opacity | 整体透明度 | number | string | |
| radius | 矩形圆角半径 | number | string | number[] | |
| shadowBlur | 阴影模糊程度 | number | |
| shadowColor | 阴影颜色 | string | |
| shadowOffsetX | 阴影 X 方向偏移 | number | |
| shadowOffsetY | 阴影 Y 方向偏移 | number | |
| stroke | 描边颜色 | string | Pattern | null | #1677FF |
| strokeOpacity | 描边透明度 | number | string | |
| visibility | 可见性 | visible | hidden |
|
| zIndex | 渲染层级 | number | 2 |
自定义框样式示例(虚线外框 + 渐变色填充):
const graph = new Graph({
behaviors: [
{
type: 'brush-select',
key: 'brush-select',
style: {
width: 0,
height: 0,
lineWidth: 4,
lineDash: [2, 2], // 虚线外框
// 渐变炫彩填充
fill: 'linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%),linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%),linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%)',
stroke: 'pink',
fillOpacity: 0.2,
zIndex: 2,
pointerEvents: 'none',
},
},
],
});
5.7 enable 与 onSelect
- enable:
boolean | (event) => boolean,默认true。传入函数时可在指针事件(如右键、命中元素类型等)上动态决定是否启用框选,对应源码 validate() 的isFunction(enable) ? enable(event) : !!enable; - onSelect:框选结算后的回调,参数
states: Record<ID, State | State<a href="https://link.gitcode.com/i/68181162c98e389975b168f8889c6fd1" target="_blank">]>为每个被框选元素应设置的状态集合。它在状态写入图之前触发(源码 [updateElementsStates()),可用于拦截、记录或扩展框选结果。
六、源码级实现原理
6.1 事件生命周期
brush-select 继承自 BaseBehavior,在构造函数中绑定三个指针事件与一个点击事件(bindEvents()):
POINTER_DOWN(按下):校验validate()(是否启用)与isKeydown()(trigger 是否匹配),通过后按当前画布缩放比例折算lineWidth(style.lineWidth / graph.getZoom(),保证放大/缩小时选区边框视觉宽度一致),创建Rect并追加到画布,记录起点;POINTER_MOVE(移动):以起点与当前光标点计算矩形的x、y、width、height并更新矩形;若immediately && mode === 'default'则实时调用updateElementsStates();POINTER_UP(松开):结算最终选中状态并清理选区矩形;CLICK(单击空白):清除画布上所有元素的指定状态(clearElementsStates()),实现"点击空白取消选中"。
6.2 光标坐标与 HTML 节点兼容
源码 getCursorPoint() 处理了 HTML 类型节点下的框选兼容(修复 issue #7182):当事件 targetType 为 node/combo 且 nativeEvent.target 不是 HTMLCanvasElement 时,说明事件由 HTML 节点触发,此时通过 graph.getCanvasByClient([client.x, client.y]) 将浏览器坐标转换为画布坐标,保证跨 HTML 节点拖拽框选时选区位置不偏移。
6.3 命中判定:点与多边形
选区本身是一个矩形,但其四个顶点以 Point<a href="https://link.gitcode.com/i/2615f53b56c50fa8882207db4d4fa1ee" target="_blank">] 形式传入 updateElementsStates(),最终通过 [packages/g6/src/utils/point.ts 中的 isPointInPolygon 做点与多边形包含判定,判定对象为元素的中心点(graph.getElementPosition(id))。因此从架构上看,框选命中逻辑是通用的"点在多边形内"检测,后续也易于扩展为任意形状的选区。
七、测试验证
官方单元测试 packages/g6/tests/unit/behaviors/brush-select.spec.ts 系统性地验证了各配置行为:
- 模拟
POINTER_DOWN → POINTER_MOVE → POINTER_UP事件流完成框选,CLICK清除状态; - 逐一验证
trigger: 'drag'、trigger: 'shift'(配合KEY_DOWN/KEY_UP)、immediately: true以及mode: union / diff / intersect的选中结果; - 通过
graph.setBehaviors([{ type: 'brush-select', ... }])切换配置并断言快照; - 通过
graph.zoomTo(5)验证缩放场景下选区边框的宽度折算逻辑。
对应的测试快照存放于 packages/g6/tests/snapshots/behaviors/brush-select/,涵盖 brush-selecting-*(拖拽中)、brush-selected-*(选中后)及 brush-selecting-zoom(缩放场景)等状态;参考 Demo 位于 packages/g6/tests/demos/behavior-brush-select.ts。
八、完整实战示例
将框选、自定义样式与自定义状态结合,构建一个开箱即用的完整图应用:
import { Graph } from '@antv/g6';
const graph = new Graph({
container: 'container',
data: {
nodes: [
{ id: 'node-1', style: { x: 200, y: 250 } },
{ id: 'node-2', style: { x: 250, y: 200 } },
{ id: 'node-3', style: { x: 300, y: 250 } },
{ id: 'node-4', style: { x: 250, y: 300 } },
],
edges: [
{ source: 'node-1', target: 'node-2' },
{ source: 'node-2', target: 'node-3' },
{ source: 'node-3', target: 'node-4' },
{ source: 'node-4', target: 'node-1' },
],
},
behaviors: [
{
key: 'brush-select',
type: 'brush-select',
enable: true,
animation: false,
mode: 'default', // union | intersect | diff | default
state: 'selected', // 'active'、'selected'、'inactive'、自定义状态
trigger: [], // ['Shift', 'Alt', 'Control', 'Meta', ...]
style: {
lineWidth: 4,
lineDash: [2, 2],
stroke: 'pink',
fillOpacity: 0.2,
zIndex: 2,
pointerEvents: 'none',
},
onSelect: (states) => {
console.log('框选结果:', states);
},
},
],
});
graph.render();
九、注意事项与最佳实践
- 避免与
drag-canvas冲突:若需直接拖拽框选(trigger: []),请勿同时启用drag-canvas,否则拖拽事件会被平移画布行为抢占;官方建议通过['shift']等修饰键区分两种交互; immediately依赖mode: 'default':在其他模式下该配置不生效,调试时注意联动;- 边的选中依赖两端节点:框选区域只覆盖边的一部分、而未覆盖其两端节点时,该边不会被选中,这是源码中明确的规则;
- 动态配置:优先使用带
key的对象形式声明,配合graph.updateBehavior()可在运行时平滑切换触发键、模式、状态与框样式,而无需重建图实例。
通过以上配置与原理,你可以依据业务需要自由组合 trigger、mode、state 与 style,在 G6 中实现类画图工具级别的批量选择体验。