跳到主要内容

图表散点与大量标记

Chart 库内部多半还是 Canvas 2D 或 SVG。自己画时,先分清 轴/网格(少)系列点(多)。点一多,2D 的同步 fill 会先于「算法不够」把帧打爆。

1. 分层

网格 + 坐标轴:几乎静态,resize/换域才重绘
系列:数据 dirty 才重绘
十字准星 + tooltip:DOM 或动态层,跟指针

轴用世界(数据)→ view 的独立 affine,不一定复用白板 Camera,但公式同类:

export interface PlotScale {
xMin: number;
xMax: number;
yMin: number;
yMax: number;
padLeft: number;
padRight: number;
padTop: number;
padBottom: number;
width: number;
height: number;
}

export function dataToView(s: PlotScale, x: number, y: number): { x: number; y: number } {
const w = s.width - s.padLeft - s.padRight;
const h = s.height - s.padTop - s.padBottom;
return {
x: s.padLeft + ((x - s.xMin) / (s.xMax - s.xMin)) * w,
y: s.padTop + (1 - (y - s.yMin) / (s.yMax - s.yMin)) * h,
};
}

Y 向上是数据惯例,Canvas Y 向下,必须翻转。漏翻转是「图是对的但倒了」。

2. 点怎么画

数量级策略
< 2k按颜色分桶 Path2D 一次 fill,见 性能
2k–2万视口 LOD:聚合到像素;一像素多点只画一次
> 2万仍要每点交互WebGL / 专用 chart(ECharts GL、regl)
export function paintDedupedPoints(
ctx: CanvasRenderingContext2D,
scale: PlotScale,
points: readonly { x: number; y: number }[],
radius: number,
): void {
const seen = new Set<number>();
const path = new Path2D();
for (const p of points) {
const v = dataToView(scale, p.x, p.y);
const ix = Math.round(v.x);
const iy = Math.round(v.y);
const key = (ix << 16) ^ iy;
if (seen.has(key)) continue;
seen.add(key);
path.moveTo(v.x + radius, v.y);
path.arc(v.x, v.y, radius, 0, Math.PI * 2);
}
ctx.fill(path);
}

这是屏幕像素去重,不是统计 bin。密度图要用真正的 grid count 再上色。

3. 拾取

不要 isPointInPath 对两万个圆。把点放进 网格索引,查询鼠标附近 1–2 格,再比距离。

Tooltip 用 DOM overlay,内容可复制、可读屏。Canvas 里 fillText 当 tooltip 是倒退。

4. 动画与大数据

追加流式点:只脏系列层。不要每秒重建轴。时间窗移动是改 xMin/xMax + 系列重绘,网格线按新域生成。

5. 失败形态

症状原因
图倒了Y 没翻转
1万点卡成幻灯片每点 beginPath+fill,还有阴影
hover 错点命中用了数据坐标去比 CSS
缩轴后线宽狂变线宽画在数据空间
Retina 点变椭圆x/y scale 各算各的 dpr

权威资料

  • 无图表专规。拾取与分桶见本体系 03/06;超量用 WebGL 选型

核对日期:2026-08-26