Visx React 可视化 — Airbnb 出品的 React + D3 可视化原语库¶
一句话说明¶
Visx(原 vx)是 Airbnb 开源的可视化库,把 D3 的计算能力封装成 React 组件,让你用 JSX 写图表,无需直接操作 DOM,是 React 项目中 D3 的最佳拍档,当前版本 v3.x。
安装与配置¶
# 按需安装(Visx 是模块化的,只装要用的)
npm install @visx/group @visx/shape @visx/scale @visx/axis
# 常用模块一览
npm install @visx/group # 分组容器(SVG <g> 的 React 封装)
npm install @visx/shape # 形状:Bar, Line, Area, Pie 等
npm install @visx/scale # 比例尺:scaleLinear, scaleBand 等
npm install @visx/axis # 坐标轴
npm install @visx/tooltip # 悬浮提示
npm install @visx/zoom # 缩放交互
npm install @visx/gradient # 渐变色
npm install @visx/text # 文字标签
# 检查版本
npm list @visx/shape
核心用法¶
// ── 基础柱状图组件 ──
import { scaleBand, scaleLinear } from '@visx/scale'; // 比例尺
import { Bar } from '@visx/shape'; // 矩形形状
import { Group } from '@visx/group'; // SVG 分组
import { AxisBottom, AxisLeft } from '@visx/axis'; // 坐标轴
// 数据类型定义
interface DataPoint {
label: string; // X 轴标签
value: number; // Y 轴数值
}
const data: DataPoint[] = [
{ label: 'Gene1', value: 3.2 }, // 基因表达量数据
{ label: 'Gene2', value: 1.8 },
{ label: 'Gene3', value: 4.5 },
{ label: 'Gene4', value: 2.1 },
];
// 取值函数(Visx 约定用 accessor 函数)
const getLabel = (d: DataPoint) => d.label; // 获取 X 轴值
const getValue = (d: DataPoint) => d.value; // 获取 Y 轴值
function BarChart({ width = 500, height = 300 }) {
const margin = { top: 20, right: 20, bottom: 40, left: 50 }; // 边距
const innerWidth = width - margin.left - margin.right; // 内部宽度
const innerHeight = height - margin.top - margin.bottom; // 内部高度
// 定义 X 比例尺(分组型)
const xScale = scaleBand<string>({
range: [0, innerWidth], // 输出像素范围
domain: data.map(getLabel), // 输入:所有标签
padding: 0.3, // 柱子间距
});
// 定义 Y 比例尺(线性)
const yScale = scaleLinear<number>({
range: [innerHeight, 0], // 注意反向:大值在上方
domain: [0, Math.max(...data.map(getValue))], // 0 到最大值
});
return (
<svg width={width} height={height}>
<Group top={margin.top} left={margin.left}> {/* 偏移留出边距 */}
{data.map((d) => {
const barHeight = innerHeight - (yScale(getValue(d)) ?? 0); // 柱子高度
return (
<Bar
key={getLabel(d)} // React key
x={xScale(getLabel(d))} // X 坐标
y={innerHeight - barHeight} // Y 坐标(从顶部开始)
width={xScale.bandwidth()} // 柱子宽度
height={barHeight} // 柱子高度
fill="#4e79a7" // 填充颜色
/>
);
})}
<AxisBottom top={innerHeight} scale={xScale} /> {/* 底部 X 轴 */}
<AxisLeft scale={yScale} /> {/* 左侧 Y 轴 */}
</Group>
</svg>
);
}
实战案例¶
// ── 折线图 + 悬浮提示 ──
import { LinePath } from '@visx/shape'; // 折线形状
import { curveMonotoneX } from '@visx/curve'; // 平滑曲线算法
import { useTooltip, TooltipWithBounds } from '@visx/tooltip'; // 提示框
import { localPoint } from '@visx/event'; // 获取鼠标坐标
function LineChart({ width = 500, height = 300 }) {
const {
showTooltip, // 显示提示
hideTooltip, // 隐藏提示
tooltipData, // 当前提示数据
tooltipLeft, // 提示框 X 位置
tooltipTop, // 提示框 Y 位置
tooltipOpen, // 提示是否显示
} = useTooltip<DataPoint>();
const data = [
{ label: 'T0', value: 1.0 },
{ label: 'T1', value: 2.5 },
{ label: 'T2', value: 4.0 },
{ label: 'T3', value: 3.2 },
];
const xScale = scaleBand({ domain: data.map(d => d.label), range: [0, 400] });
const yScale = scaleLinear({ domain: [0, 5], range: [250, 0] });
return (
<div style={{ position: 'relative' }}>
<svg width={500} height={300}>
<Group top={20} left={50}>
<LinePath
data={data}
x={d => (xScale(d.label) ?? 0) + xScale.bandwidth() / 2} // 居中对齐
y={d => yScale(d.value) ?? 0} // Y 坐标
stroke="#e15759" // 线条颜色
strokeWidth={2} // 线条粗细
curve={curveMonotoneX} // 平滑曲线
/>
{/* 悬浮检测区域 */}
<rect
width={400} height={250} fill="transparent"
onMouseMove={(e) => {
const point = localPoint(e); // 获取鼠标位置
showTooltip({ tooltipData: data[0], tooltipLeft: point?.x, tooltipTop: point?.y });
}}
onMouseLeave={hideTooltip} // 鼠标离开隐藏提示
/>
</Group>
</svg>
{/* 提示框 */}
{tooltipOpen && (
<TooltipWithBounds left={tooltipLeft} top={tooltipTop}>
<strong>{tooltipData?.label}</strong>: {tooltipData?.value}
</TooltipWithBounds>
)}
</div>
);
}
常见报错与解决¶
| 报错 | 原因 | 解决方法 |
|---|---|---|
Module not found: @visx/xxx | 没安装对应模块 | npm install @visx/xxx |
xScale(d.label) 返回 undefined | domain 里没有该 label | 检查数据与 domain 是否一致 |
| 坐标轴文字被截断 | margin 不够 | 增大 margin.bottom 或 margin.left |
useTooltip 未生效 | 没有 position: relative | 父容器加 style={{ position: 'relative' }} |
| 图表在 SSR 报错 | D3 依赖浏览器 API | 用 dynamic(() => import('./Chart'), { ssr: false }) |
速查表¶
# 常用 @visx 包
@visx/shape # Bar, Line, Area, Pie, Arc
@visx/scale # scaleBand, scaleLinear, scaleLog, scaleTime
@visx/axis # AxisBottom, AxisTop, AxisLeft, AxisRight
@visx/grid # GridRows, GridColumns(网格线)
@visx/curve # curveLinear, curveMonotoneX, curveBasis
@visx/tooltip # useTooltip, Tooltip, TooltipWithBounds
@visx/annotation # 注解标注
@visx/brush # 区域选择刷取
@visx/zoom # 缩放交互
# 官方文档:https://airbnb.io/visx/
# 示例库:https://airbnb.io/visx/gallery