G6 框选交互 BrushSelect 完整指南:配置参数、选择模式与源码实现解析

原创2026-09-22 23:30:001,719 阅读
文章标签:数据可视化前端图表库

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 中实现类画图工具级别的批量选择体验。

登录后查看全文
G6