- 数据可视化
- 前端
【免费下载链接】F2
📱📈An elegant, interactive and flexible charting library for mobile.
本文以 F2 仓库中@antv/f2-algorithm子包的 CHANGELOG.md 为发展脉络,结合其 源码、测试用例 与 README,系统讲解该算法包提供的两类数据降采样能力——rateDownSample(常见统计采样)与lttbDownSample(最大三角形三分桶采样),覆盖参数配置、源码级实现原理、边界条件处理以及在 F2 折线图中的接入方式。读完本文,你将能理解两种采样算法的取舍差异,掌握在移动端图表中为大体积时序数据做降采样、保持图表形态与性能平衡的完整方案。
一、f2-algorithm 在 F2 生态中的定位
@antv/f2-algorithm是 F2 移动端图表库的算法扩展包,其 package.json 中的描述为 "F2 algorithm extension",关键词包含chart、mobile、visualization、sample等,sideEffects声明为false,便于按需打包。它并不依赖 F2 主包本身,而是独立提供可复用的数据处理算法,再与 F2 的Chart/Line组件组合使用。
该包的入口文件 src/index.ts 非常精简,只做一件事——导出两个降采样函数:
import lttbDownSample from './lttbDownSample'; import rateDownSample from './rateDownSample'; export { lttbDownSample, rateDownSample };也就是说,整个算法包的能力收敛为两个 API:
| API | 算法类型 | 适用场景 |
|---|---|---|
rateDownSample | 常见统计采样(nearest / max / min) | 快速减点,追求性能与简单可控 |
lttbDownSample | Largest Triangle Three Buckets | 高质量保留趋势与波动形态,适合视觉还原度要求高的场景 |
从 CHANGELOG.md 的版本记录看,该包经历了从命名、依赖配置到采样函数本身的多次修复,本节后文的版本演进部分会结合这些记录展开。
二、rateDownSample:常见统计采样
2.1 函数签名与参数说明
rateDownSample定义在 src/rateDownSample.ts,其核心参数通过OptionsProps声明:
export interface OptionsProps { sampling: 'nearest' | 'max' | 'min' | Function; /* 周期 */ rate: number; dimension: string; }| 参数 | 类型 | 默认值 | 含义 |
|---|---|---|---|
sampling | 'nearest' \| 'max' \| 'min' \| Function | 'nearest' | 采样策略;也支持传入自定义函数 |
rate | number | 5 | 采样周期/抽稀比例,含义是"每隔 rate 个点取 1 个代表点" |
dimension | string | 'value' | 参与极值比较的数值字段名,例如折线图的 y 轴字段 |
函数入口处的默认值解构与前置校验是理解其行为的关键(rateDownSample.ts#L63-L75):
export default function rateDownSample(data, options?: OptionsProps) { const { sampling = 'nearest', rate = 5, dimension = 'value' } = options; let sampler; if (isFinite(rate) && rate > 1) { if (isString(sampling)) { sampler = samplers[sampling]; } else if (isFunction(sampling)) { sampler = sampling; } return downSample(data, rate, sampler, dimension); } return data; }两个关键约束:
rate必须是大于 1 的有限数,否则函数直接原样返回data,不做任何采样;sampling既支持内置字符串,也支持传入自定义函数,扩展性强。
2.2 分帧采样核心:downSample
downSample(rateDownSample.ts#L11-L31)是rateDownSample的内部实现,逻辑非常直观——按frameSize = Math.floor(rate)将数据切成若干"帧"(bucket),每帧内调用一次sampler取一个代表点:
function downSample(data, rate, sampler, dimension) { const sampled = []; let sampledIndex = 0; const len = data.length; let frameSize = Math.floor(rate); for (let i = 0; i < len; i += frameSize) { // Last frame frameSize = frameSize > len - i ? len - i : frameSize; const frameValues = []; for (let k = 0; k < frameSize; k++) { frameValues[k] = data[k + i]; } const value = sampler(frameValues, dimension); sampled[sampledIndex++] = value; } return sampled; }值得注意的细节:最后一帧的长度会自动截断(frameSize = frameSize > len - i ? len - i : frameSize),保证采样过程不会越界或丢失尾部数据,这一点正是 CHANGELOG 中相关边界问题修复所覆盖的场景。
2.3 内置采样器:nearest / max / min
内置采样器定义在 rateDownSample.ts#L33-L54:
const samplers = { max: (frame, dimension) => { const max = -Infinity; let maxData; for (let i = 0; i < frame.length; i++) { frame[i][dimension] > max && (maxData = frame[i]); } return maxData || NaN; }, min: (frame, dimension) => { const min = Infinity; let minData; for (let i = 0; i < frame.length; i++) { frame[i][dimension] < min && (minData = frame[i]); } return minData || NaN; }, // TODO 中位数 median nearest: (frame) => { return frame[0]; }, };nearest(最近邻):直接取每帧的第一个数据点。实现最廉价、速度最快,但如测试快照所示,会大幅丢失局部波动细节,曲线形态最粗糙。max/min(极值采样):在每帧内按dimension字段找出最大值/最小值对应的整条数据记录并返回。适合需要保留"峰值/谷值"语义的场景,例如股票 K 线、波峰波谷监测。需要注意的是,如果帧内没有满足比较条件的数据,可能返回NaN(源码中的return maxData || NaN)。- 自定义函数:
sampling也接受Function,签名约定为(frameValues, dimension) => value,可以从帧内按业务规则挑选代表点。
源码中// TODO 中位数 median的注释表明中位数采样是一个计划中的扩展方向,目前尚未实现。
三、lttbDownSample:最大三角形三分桶(LTTB)采样
3.1 LTTB 算法思想
LTTB(Largest Triangle Three Buckets,最大三角形三分桶)是面向时序数据的高质量降采样算法。其核心思想是:把数据等分为若干桶,对每个桶,用"上一桶已选点 A、本桶候选点 B、下一桶平均点 C"构造三角形,保留面积最大的那个点——三角形面积越大,说明该点对曲线形态的影响越关键,从而在减点的同时最大限度保留主趋势、峰值与谷值位置。
实现位于 src/lttbDownSample.ts,参数接口同样为OptionsProps(rate与dimension,默认值分别为5和'value')。
3.2 前置边界条件
export default function lttbDownSample(data, options?: OptionsProps) { const { rate = 5, dimension = 'value' } = options; const len = data.length; const targetCount = len / rate; if (rate >= len || targetCount < 2) { return data; } ...(lttbDownSample.ts#L17-L24)
LTTB 有两个明确的边界保护:
rate >= len:采样比例大于等于数据量,无需采样,原样返回;targetCount < 2:目标点数不足 2 个时算法无法成立(至少需要首尾两个锚点),原样返回。
这两条正是 CHANGELOG 中5.0.34修复项"data数量小于rate"(对应 issue #1880)所解决的边界问题——当数据量小于rate时,降采样不再报错或产生异常结果,而是安全地回退为返回原始数据。
3.3 算法主循环的源码解读
主循环(lttbDownSample.ts#L26-L81)分四步:
- 计算桶大小:
bucketSize = Math.floor((len - 2) / (targetCount - 2)),因为首尾两点始终保留,实际只需在中间挑选targetCount - 2个点。 - 保留首点:
sampled[0] = data[0],并将a = 0作为初始锚点 A。 - 逐桶挑选最大面积点:对第 C 桶先求
dimension字段的平均值得到平均点(avgX, avgY),然后在第 B 桶内遍历每个候选点,计算"点 A、候选点 B、平均点 C"三点构成三角形的面积:
area = Math.abs( (pointA_x - avgX) * (Number(data[rangeOffs][dimension]) - pointA_y) - (pointA_x - rangeOffs) * (avgY - pointA_y) ) * 0.5;面积最大者即被选中,同时更新下一轮锚点a = nextA。 4.保留末点:循环结束后将data[len - 1]追加到结果尾部。
从实现细节看,dimension字段值会经Number()强转,因此即使是字符串形式的数值数据(如测试中的'0.516')也能被正确处理;x 轴则默认按数据下标参与计算。整体上 LTTB 是"保形优先"的采样算法,代价是比nearest更高的计算量——对每一桶都要遍历候选点计算三角形面积,时间复杂度为O(n)但常数更大。
四、在 F2 图表中的接入方式
4.1 README 中的最小用法
README.md 给出了最简接入示例:先对数据做降采样,再把采样结果交给Chart渲染:
import dataSample from '@antv/f2-algorithm'; const sampleData = dataSample({ data, sampling: 'nearest', rate: 7, }); <Canvas pixelRatio={1}> <Chart data={sampleData}> <Line x="date" y="value" /> </Chart> </Canvas>;注意 README 示例使用的是包默认导出风格;而实际 入口文件 导出的是具名的lttbDownSample与rateDownSample两个函数,测试中也是按具名导入使用的(见下文)。实际接入时建议按具名方式导入,例如:
import { lttbDownSample } from '@antv/f2-algorithm'; const sampleData = lttbDownSample(data, { rate: 7, dimension: 'value' });4.2 测试用例中的完整接入链路
test/sample.test.tsx 用 4 组用例演示了两类 API 与 F2 渲染管线的完整配合,每个用例都通过createContext创建画布上下文、渲染后与快照做像素级比对:
- origin data:不采样,直接渲染原始数据作为对照组;
- lttb sample:
lttbDownSample(data, { rate: 7, dimension: 'value' })后渲染; - rate 小于可抽个数:构造 9 条数据、
rate: 7的极端小样本场景,验证边界保护逻辑; - nearest sample:
rateDownSample(data, { sampling: 'nearest', rate: 7, dimension: 'value' })后渲染。
测试数据取自远程 JSON(字段date/value),value统一经Number()转换为数值后进入采样。这与真实业务中"接口返回字符串数值、图表前先归一化"的做法一致。对应的渲染快照存放于 test/image_snapshots,从快照对比可以直观看到三种渲染结果的差异:原始数据点最密集、锯齿感最强;LTTB 减点后主趋势、峰值谷值位置与原始数据几乎一致,仅抹掉细碎波动;nearest 采样后数据最稀疏,局部细节被大幅简化。
五、从 CHANGELOG 看算法包的版本演进
CHANGELOG.md 记录了该包自 5.0.31 以来的完整演进。由于 F2 采用 lerna monorepo 管理(见根目录 lerna.json,其 publish 配置启用了conventionalCommits),大部分版本为纯版本对齐(Note: Version bump only for package @antv/f2-algorithm),但其中有 4 次修复值得关注:
| 版本 | 日期 | 修复内容 | 对应源码行为 |
|---|---|---|---|
| 5.0.31 | 2023-09-05 | package name(#1840) | 包正式命名为@antv/f2-algorithm |
| 5.0.34 | 2023-11-02 | data 数量小于 rate(#1880) | lttbDownSample/rateDownSample增加边界回退(数据量不足时原样返回) |
| 5.5.0 | 2024-05-30 | algorithm package.json(#1963) | 修正包的依赖与发布配置(@antv/util、tslib等) |
| 5.7.0 | 2024-10-09 | sample func(#2004) | 采样函数本身的缺陷修复 |
结合 package.json 中当前版本5.7.0可以确认:5.7.0 是当前仓库中该包的最新版本,以上修复均已包含。这套"CHANGELOG + conventional commits + lerna 版本对齐"的流程,也保证了每个历史问题都有据可查。
从演进脉络中可以看到两个工程实践要点:
- 边界条件优先被修复:"data 数量小于 rate" 这类边界问题在真实业务(尤其是小样本或刚起步的数据源)中极易触发,因此两个采样函数都设计了"参数不满足条件即原样返回"的安全兜底;
- 算法包保持轻量独立:多次修复集中在包命名、package.json 配置层面,核心算法(
downSample分帧 +samplers极值/最近邻 + LTTB 三角形面积)自 5.0.x 起即保持稳定,说明其 API 设计与实现已收敛成熟。
六、实战选型建议与参数速查
6.1 两种采样如何选
- 对趋势保真度要求高(如金融行情、监控曲线、需要和原始数据"看起来几乎一样"的场景):优先
lttbDownSample,它通过三角形面积最大化保留峰值、谷值与整体形态,代价是略高的 CPU 开销; - 追求极致性能、数据量极大且允许细节损失(如日志曲线、大规模埋点展示):优先
rateDownSample的nearest采样,常数时间取帧首点,开销最小; - 需要语义化极值(如"只看波峰波谷""每帧保留最高点"):用
rateDownSample的max/min采样,配合dimension指定参与比较的数值字段。
6.2 参数速查表
| 参数 | 默认值 | 适用函数 | 说明 |
|---|---|---|---|
rate | 5 | 两者 | 采样比例。rateDownSample要求rate > 1的有限数;lttbDownSample要求rate < len且len / rate >= 2,否则原样返回 |
dimension | 'value' | 两者 | 参与比较/计算的数值字段名,源码会经Number()强转,支持字符串数值 |
sampling | 'nearest' | rateDownSample独有 | 'nearest' \| 'max' \| 'min' \| Function,内置极值采样器按dimension找帧内最大/最小记录 |
两个函数的通用调用模式:
import { lttbDownSample, rateDownSample } from '@antv/f2-algorithm'; // 保形采样:5000 点 → 约 714 点(rate = 7) const lttbData = lttbDownSample(rawData, { rate: 7, dimension: 'value' }); // 快速采样:每帧取首个点 const nearestData = rateDownSample(rawData, { sampling: 'nearest', rate: 7, dimension: 'value' }); // 极值采样:每帧保留 value 最大的记录 const maxData = rateDownSample(rawData, { sampling: 'max', rate: 7, dimension: 'value' });七、总结
@antv/f2-algorithm以两个函数覆盖了移动端图表最典型的降采样诉求:rateDownSample提供快速、可定制的统计采样(nearest / max / min / 自定义函数),lttbDownSample提供高质量的保形采样。从 CHANGELOG.md 到 源码 再到 测试,三者互相印证:边界条件的兜底处理、默认参数的语义(rate = 5、dimension = 'value'、sampling = 'nearest')以及渲染快照的对比效果,共同构成了一个"开箱即用、行为可预期"的算法扩展包。在实际项目中,只需在渲染前对数据做一次降采样并传入Chart,即可在大数据量场景下兼顾流畅渲染与形态还原。
- 数据可视化
- 前端
【免费下载链接】F2
📱📈An elegant, interactive and flexible charting library for mobile.
相关推荐
使用 @antv/f2-algorithm 为 F2 移动端图表做高性能数据采样
使用 @antv/f2 algorithm 为 F2 移动端图表做高性能数据采样 数据采样(down sampling / data sampling)是移动端
数据可视化前端F2 词云图扩展 @antv/f2-wordcloud 实战指南:从数据布局到遮罩形状定制
F2 词云图扩展 @antv/f2 wordcloud 实战指南:从数据布局到遮罩形状定制 本篇指南围绕 F2 生态中的独立扩展包 @antv/f2 wordc
数据可视化前端3D点云降采样革命:Open3D VoxelGrid算法原理与实战指南
3D点云降采样革命:Open3D VoxelGrid算法原理与实战指南 在3D点云处理中,你是否遇到过这些痛点?百万级点云导致算法运行缓慢?复杂场景下的空间分析
计算机视觉图形学3D渲染科学计算
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考