跳到主要内容

图表与数据可视化

图表的文档是 数据空间上的几何,投影进用户空间(viewBox),再被 CSS 盒子当 viewport。轴、图例、可点的点必须是节点;过密的点、热力、刷选蒙版可以光栅。D3 最多管 join 和比例尺,见 自研 vs D3

1. 场景怎么切

世界:数据域(xMin..xMax, yMin..yMax)→ 用户空间(plot 区)
相机:通常是静态 viewBox;刷选/平移改的是比例尺域,不是 CSS scale
g.axis / g.grid:少,几乎静态
g.series:数据 dirty 才 join
canvas 层(可选):散点密度、面积填充烘焙
DOM:tooltip、数据表(读屏真值)

必须是节点:坐标轴文字、图例项、当前选中的点/柱、可 tab 的标注。它们要有对象语义。

可以光栅:同一像素上叠了几十个点的系列、连续色带、stream 新点。用离屏 canvas 对齐同一 plot 矩形,指针命中仍查你的空间索引,不要对两万个 <circle>elementFromPoint

export interface PlotScale {
xMin: number;
xMax: number;
yMin: number;
yMax: number;
plot: { x: number; y: number; w: number; h: number };
}

export function dataToUser(s: PlotScale, x: number, y: number): { x: number; y: number } {
return {
x: s.plot.x + ((x - s.xMin) / (s.xMax - s.xMin)) * s.plot.w,
y: s.plot.y + (1 - (y - s.yMin) / (s.yMax - s.yMin)) * s.plot.h,
};
}

Y 向上是数据惯例,SVG Y 向下,必须翻转。漏翻转是「数对了图倒了」。线宽画在用户空间:viewBox 随 CSS 变大时描边按矢量比例变;若产品要「屏幕上恒 1px」,用 vector-effect: non-scaling-stroke 或把线画在不随数据域缩放的 overlay 层。

2. 节点预算

量级策略
< 500 可交互图元纯 SVG join
500–5k系列合并 path(同色一条 d),交互用索引而不是每点一节点
> 5k 仍要每点 hoverCanvas / WebGL 系列层 + SVG 轴

不要给每个数据点一个带 filter 的 path。滤镜强制光栅,节点一多就等于每帧上传几千张小位图。预算见 DOM 节点成本

逆变换做刷选:指针先经 getScreenCTM 进用户空间,再按 plot 矩形反解数据域。不要用 offsetX 当数据。

export function userToData(s: PlotScale, ux: number, uy: number): { x: number; y: number } {
return {
x: s.xMin + ((ux - s.plot.x) / s.plot.w) * (s.xMax - s.xMin),
y: s.yMax - ((uy - s.plot.y) / s.plot.h) * (s.yMax - s.yMin),
};
}

3. 不适用

  • 把 Chart 当编辑器:选中、对齐、连接桩是流程图,不要用 ordinal scale 硬撑。
  • tooltip 用 SVG text 追鼠标:IME、换行、复制都差,用 DOM overlay。
  • 时间轴动画去改几千个 cx:改比例尺 + 重算 path,或只脏 canvas 层。

4. 失败形态

症状原因
图倒了Y 没翻转
缩窗后线变成绳子线宽在数据空间,域一变比例炸
hover 错点用 CSS 像素去比数据坐标,没经 dataToUser 逆变换
1 万点卡成幻灯片每点一个带阴影的 DOM
上了 D3 仍点不准zoom transform 和 viewBox 叠了两套相机

权威资料

核对日期:2026-08-26