跳转至

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) 返回 undefineddomain 里没有该 label检查数据与 domain 是否一致
坐标轴文字被截断margin 不够增大 margin.bottom 或 margin.left
useTooltip 未生效没有 position: relative父容器加 style={{ position: 'relative' }}
图表在 SSR 报错D3 依赖浏览器 APIdynamic(() => 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