图表与数据可视化
图表的文档是 数据空间上的几何,投影进用户空间(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 仍要每点 hover | Canvas / 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