- 数据可视化
- 前端
- 图表库
【免费下载链接】visx
🐯 visx | visualization components
visx 是 Airbnb 开源的一套基于 React + D3 的可视化组件库,其中@visx/brush用于在图表或坐标轴上框选子区域,是实现"缩放到选中区间"、"时间范围选择"、"联动过滤"等交互的核心组件。本文将围绕 packages/visx-brush/Readme.md 展开,结合仓库源码与测试,系统讲解 Brush 的安装方式、核心 Props、内部工作流程与回调数据结构,帮助你直接在项目中落地一套可运行、可定制的框选交互。
安装
@visx/brush是一个独立的 npm 包,安装方式与 visx 其他模块一致:
npm install --save @visx/brush从 package.json 可以看到,它同时提供 CommonJS(lib/index.js)与 ESM(esm/index.js)两种产物,声明了"sideEffects": false,可被打包工具安全地做 Tree Shaking。它的运行依赖包括@visx/drag、@visx/event、@visx/group、@visx/scale、@visx/shape与classnames,其中拖拽核心逻辑由@visx/drag提供。
注意:
react是 peerDependency,要求 React 18 或 19(^18.0.0 || ^19.0.0)。
包的唯一对外导出是Brush组件(见 src/index.ts):
export { default as Brush } from './Brush'; export type * from './types';也就是说,你在使用时只需要import { Brush } from '@visx/brush'即可。
Brush 是什么
官方文档对其定位只有一句话:A brush allows you to select a sub-region of your chart or axis(Brush 允许你选择图表或坐标轴的子区域)。
一个典型的场景是:在图表下方放置一个迷你总览图(overview),用户用鼠标在总览图上拖出一个矩形选区,主图(detail view)随即缩放到该区间——这正是 visx-demo 中 brush 示例(如pages/brush)呈现的效果。Brush 既能横向、纵向框选,也能整体拖动选区、拖拽选区边缘或角落调整大小。
核心 Props 一览
Brush组件本身的全部 Props 定义在 src/Brush.tsx 中,下面按用途分组说明。
坐标与尺寸
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
xScale | Scale | null | x 轴比例尺,用于把选区像素范围反算为数据域(domain) |
yScale | Scale | null | y 轴比例尺,用于把选区像素范围反算为数据域 |
width | number | 0 | Brush 舞台(stage)宽度 |
height | number | 0 | Brush 舞台高度 |
margin | MarginShape | 全 0 | 从舞台尺寸中减去的边距(top/left/right/bottom) |
注意Scale类型来自@visx/scale的D3Scale(见 src/types.ts),因此连续型比例尺(linear/time/log)与离散型比例尺(ordinal/band/point)都可以传入,组件内部会针对两种类型分别做反算处理(下文详述)。
回调
| Prop | 类型 | 说明 |
|---|---|---|
onChange | (bounds: Bounds \| null) => void | 选区变化(拖动/缩放过程中)即触发,最常用 |
onBrushStart | (start) => void | 一次框选初始化时触发(不是移动时) |
onBrushEnd | (bounds: Bounds \| null) => void | 鼠标松开、选区尺寸更新结束时触发 |
onMouseMove | 函数 | 未拖拽时鼠标在舞台内移动 |
onMouseLeave | 函数 | 鼠标离开舞台 |
onClick | 函数 | 点击舞台 |
三个 Brush 回调都携带Bounds结构(见 src/types.ts):
type Bounds = { x0: number; // 选区的起始 x,已经是数据域(domain)值 x1: number; // 选区的结束 x,数据域值 xValues?: unknown[]; // 仅离散比例尺时存在:被框住的离散值数组 y0: number; y1: number; yValues?: unknown[]; };关键点:回调里拿到的不是像素坐标,而是经过比例尺反算后的数据域。看 src/Brush.tsx 的handleChange/convertRangeToDomain:
handleChange = (brush: BaseBrushState) => { const { onChange } = this.props; if (!onChange) return; const { x0 } = brush.extent; // 选区尚未真正形成(x0 未定义或为负)时,回传 null if (typeof x0 === 'undefined' || x0 < 0) { onChange(null); return; } onChange(this.convertRangeToDomain(brush)); };extent中的x0/x1/y0/y1是像素值,convertRangeToDomain调用getDomainFromExtent(xScale, x0, x1, SAFE_PIXEL)(SAFE_PIXEL = 2,src/Brush.tsx)将其转换为数据域。因此你的典型用法是拿到bounds后直接xScale.domain(bounds.x0 ~ x1)去更新主图,无需自己做像素换算。
选区行为
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
brushDirection | 'vertical' \| 'horizontal' \| 'both' | 'horizontal' | 允许框选的方向 |
initialBrushPosition | PartialBrushStartEnd | null | 初始选区的start/end(像素坐标,可只传 x 或 y 一部分) |
resizeTriggerAreas | ResizeTriggerAreas[] | ['left', 'right'] | 允许通过哪些边/角调整选区大小:left/right/top/bottom/topLeft/topRight/bottomLeft/bottomRight |
brushRegion | 'xAxis' \| 'yAxis' \| 'chart' | 'chart' | 框选的对象区域,决定舞台的定位与尺寸计算 |
xAxisOrientation | 'top' \| 'bottom' | 'bottom' | brushRegion='xAxis'时 x 轴所在方位 |
yAxisOrientation | 'left' \| 'right' | 'right' | brushRegion='yAxis'时 y 轴所在方位 |
selectedBoxStyle | SVGProps<SVGRectElement> | 见下 | 选中矩形(selection rect)的样式 |
disableDraggingSelection | boolean | false | 是否禁止拖动整个选区 |
disableDraggingOverlay | boolean | false | 是否禁止在舞台空白处点击创建/移动选区 |
resetOnEnd | boolean | false | 拖拽结束后是否重置 Brush 为空闲状态 |
handleSize | number | 4 | 缩放手柄尺寸,作用于所有resizeTriggerAreas |
useWindowMoveEvents | boolean | false | 是否把拖拽事件挂到 window 上(防止拖出舞台后事件丢失) |
renderBrushHandle | (props) => ReactNode | null | 自定义手柄渲染函数 |
innerRef | Ref<BaseBrush> | — | 拿到内部BaseBrush实例的引用,可调用其命令式方法 |
selectedBoxStyle的默认值(src/Brush.tsx):
{ fill: 'steelblue', fillOpacity: 0.2, stroke: 'steelblue', strokeWidth: 1, strokeOpacity: 0.8, }brushRegion:在图表上框选,还是在坐标轴上框选
brushRegion是 Brush 区别于一般框选组件的关键能力——它允许你只框选"图表的绘图区"、"x 轴区域"或"y 轴区域"。舞台的定位与尺寸计算在 src/Brush.tsx:
'chart':舞台覆盖整个图表区域,left = 0, top = 0,宽高等于width/height;'yAxis':舞台覆盖 y 轴所在边距。yAxisOrientation='right'时left = width,宽度取marginRight;'left'时left = -marginLeft,宽度取marginLeft;'xAxis':舞台覆盖 x 轴所在边距。xAxisOrientation='bottom'时top = height,高度取marginBottom;'top'时top = -marginTop,高度取marginTop。
因此使用 x 轴框选时,你需要把布局设计成"图表下方留出 x 轴高度"(即设置margin.bottom),Brush 的舞台才会落在正确的轴带区域。
内部架构:BaseBrush 与四个子组件
Brush是一个薄封装(src/Brush.tsx),负责:将像素选区换算为数据域、计算 brushRegion 舞台几何、把margin与回调透传给内部的BaseBrush。真正承载交互状态机的是BaseBrush(src/BaseBrush.tsx),它是一个 class 组件,内部维护BaseBrushState:
type BaseBrushState = { start: Point; // 选区起点(像素) end: Point; // 选区终点(像素) extent: Bounds; // 像素范围的 x0/x1/y0/y1 bounds: Bounds; // 舞台可活动边界 isBrushing: boolean; // 是否正在框选 brushingType?: 'move' | 'select' | ResizeTriggerAreas; // 当前交互类型 activeHandle: ResizeTriggerAreas | null; };BaseBrush的 render 由四种子组件拼装(目录见 src/):
- BrushOverlay(src/BrushOverlay.tsx):覆盖整个舞台的透明
Bar(来自@visx/shape),负责接收 pointer 事件以"从空白处拉出选区"或整体移动选区; - BrushSelection(src/BrushSelection.tsx):半透明选中矩形(
selectedBoxStyle),并在四边/四角渲染 8 个缩放触发区; - BrushHandle / BrushCorner(src/BrushHandle.tsx、src/BrushCorner.tsx):选区边缘与角落的缩放手柄,可用
renderBrushHandle自定义渲染; - 内部再套用
@visx/drag的Drag组件完成拖拽坐标跟踪。
BaseBrush对拖拽的三种交互做了分支处理(src/BaseBrush.tsx):
select:以起始点与实时指针位置计算 extent;move:整体平移选区,并夹取在bounds内(validDx/validDy做了边界钳制);- 边/角缩放:如
left/right/top/bottom分别只移动对应的边。
当width/height变化(如窗口缩放导致父容器尺寸变化)时,componentDidUpdate会按比例缩放现有 extent 以保持选区相对位置(src/BaseBrush.tsx)。另外,开启useWindowMoveEvents后,componentDidMount会在window上挂mouseup/mousemove监听,避免鼠标移出舞台导致拖拽中断。
比例尺反算:scaleInvert 与 getDomainFromExtent
把像素 extent 转回数据域依赖 src/utils.ts 中的两个函数:
scaleInvert(scale, value)(src/utils.ts):
- 若比例尺自带
invert(连续型如 scaleLinear/scaleTime),直接调用scale.invert(value); - 否则视为离散比例尺(ordinal/band/point),因为没有
invert,会利用range与step()计算出每个 band 的宽度,通过循环确定value落在第几个 band,返回对应索引。
getDomainFromExtent(scale, start, end, tolerentDelta)(src/utils.ts):
- 对选区两端做容差偏移(
SAFE_PIXEL = 2),把 start/end 反算为数据域; - 连续比例尺返回
{ start, end }; - 离散比例尺返回
{ values },即scale.domain()中被框住的那一段值数组。
这正是Bounds中xValues/yValues的来源。对应的单测覆盖在 test/utils.test.ts,例如验证了连续比例尺下start/end等于scale.invert(边界±容差),离散比例尺下values等于被框住的 domain 项;Brush组件的冒烟测试见 test/Brush.test.tsx。
实战示例:总览图联动主图缩放
下面给出一个可直接运行的骨架:总览图上的Brush框选结果反向驱动主图的xScale.domain。
import { useState } from 'react'; import { Brush } from '@visx/brush'; import { scaleLinear } from '@visx/scale'; import type { Bounds } from '@visx/brush/lib/types'; function BrushOverview({ width, height, data }: { width: number; height: number; data: number[] }) { // 主图的 x 域,由 Brush 回调驱动 const [xDomain, setXDomain] = useState<[number, number]>([0, data.length - 1]); const xScale = scaleLinear<number>({ range: [0, width], domain: [0, data.length - 1], }); const onBrushChange = (bounds: Bounds | null) => { if (!bounds) return; // bounds.x0/x1 已由 Brush 内部反算为数据域,可直接使用 setXDomain([bounds.x0, bounds.x1]); }; return ( <svg width={width} height={height}> {/* 这里渲染总览折线等图形 */} <Brush xScale={xScale} yScale={scaleLinear({ range: [height, 0], domain: [0, Math.max(...data)] })} width={width} height={height} margin={{ top: 0, left: 0, right: 0, bottom: 0 }} brushDirection="horizontal" resizeTriggerAreas={['left', 'right']} initialBrushPosition={{ start: { x: 0 }, end: { x: width / 2 } }} onChange={onBrushChange} /> </svg> ); }几个易错点提醒:
onChange在选区尚未形成时会回调null,代码里务必判空;- 若只需横向框选,把
brushDirection设为'horizontal',并只在resizeTriggerAreas中保留['left', 'right'],避免出现 y 方向手柄; - 想框选 x 轴而不是整个图表时,设置
brushRegion="xAxis"、xAxisOrientation="bottom",并把图表布局的下边距留给轴带; - 需要初始就有选区,用
initialBrushPosition传入像素级start/end(可只提供 x 分量); - 在窗口尺寸会变化的容器中使用时,Brush 会在
width/height变化后按比例维持选区,无需手动重置。
小结
@visx/brush以极少的对外 API(一个Brush组件、一套回调与Bounds数据结构)封装了框选交互的完整状态机:内部由BaseBrush驱动拖拽逻辑,Brush负责像素到数据域的反算与轴区/图区的舞台定位。理解brushRegion的三类舞台几何、Bounds中连续/离散比例尺的差异化输出,以及onChange(null)的空值语义,就能快速把它接入总览-详情联动、时间范围选择等典型图表交互中。
- 数据可视化
- 前端
- 图表库
【免费下载链接】visx
🐯 visx | visualization components
相关推荐
@visx/brush交互实现:构建可筛选数据范围的高级图表控件
@visx/brush交互实现:构建可筛选数据范围的高级图表控件 在数据可视化场景中,用户常常需要对图表数据进行局部观察与分析。传统静态图表无法满足动态筛选需求
数据可视化前端图表库@visx/brush多选功能:实现不连续数据范围选择的高级交互
@visx/brush多选功能:实现不连续数据范围选择的高级交互 在数据可视化应用中,用户常常需要从图表中选择特定范围的数据进行分析。传统的单选交互只能选择连续
数据可视化前端图表库@visx/zoom 完整指南:用 React 组件为图表与视口实现平移、缩放与手势交互
@visx/zoom 完整指南:用 React 组件为图表与视口实现平移、缩放与手势交互 @visx/zoom 是 visx 生态中专门用于交互缩放(zoom)
数据可视化前端图表库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考