news 2026/9/26 7:53:23

深入 F2 算法扩展包 @antv/f2-algorithm:LTTB 与常见统计降采样的原理、配置与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入 F2 算法扩展包 @antv/f2-algorithm:LTTB 与常见统计降采样的原理、配置与实战
  • 数据可视化
  • 前端

【免费下载链接】F2

📱📈An elegant, interactive and flexible charting library for mobile.

项目地址:https://gitcode.com/gh_mirrors/f2/F2
点击查看免费下载

本文以 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)快速减点,追求性能与简单可控
lttbDownSampleLargest 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'采样策略;也支持传入自定义函数
ratenumber5采样周期/抽稀比例,含义是"每隔 rate 个点取 1 个代表点"
dimensionstring'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)分四步:

  1. 计算桶大小:bucketSize = Math.floor((len - 2) / (targetCount - 2)),因为首尾两点始终保留,实际只需在中间挑选targetCount - 2个点。
  2. 保留首点:sampled[0] = data[0],并将a = 0作为初始锚点 A。
  3. 逐桶挑选最大面积点:对第 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.312023-09-05package name(#1840)包正式命名为@antv/f2-algorithm
5.0.342023-11-02data 数量小于 rate(#1880)lttbDownSample/rateDownSample增加边界回退(数据量不足时原样返回)
5.5.02024-05-30algorithm package.json(#1963)修正包的依赖与发布配置(@antv/util、tslib等)
5.7.02024-10-09sample func(#2004)采样函数本身的缺陷修复

结合 package.json 中当前版本5.7.0可以确认:5.7.0 是当前仓库中该包的最新版本,以上修复均已包含。这套"CHANGELOG + conventional commits + lerna 版本对齐"的流程,也保证了每个历史问题都有据可查。

从演进脉络中可以看到两个工程实践要点:

  1. 边界条件优先被修复:"data 数量小于 rate" 这类边界问题在真实业务(尤其是小样本或刚起步的数据源)中极易触发,因此两个采样函数都设计了"参数不满足条件即原样返回"的安全兜底;
  2. 算法包保持轻量独立:多次修复集中在包命名、package.json 配置层面,核心算法(downSample分帧 +samplers极值/最近邻 + LTTB 三角形面积)自 5.0.x 起即保持稳定,说明其 API 设计与实现已收敛成熟。

六、实战选型建议与参数速查

6.1 两种采样如何选

  • 对趋势保真度要求高(如金融行情、监控曲线、需要和原始数据"看起来几乎一样"的场景):优先lttbDownSample,它通过三角形面积最大化保留峰值、谷值与整体形态,代价是略高的 CPU 开销;
  • 追求极致性能、数据量极大且允许细节损失(如日志曲线、大规模埋点展示):优先rateDownSample的nearest采样,常数时间取帧首点,开销最小;
  • 需要语义化极值(如"只看波峰波谷""每帧保留最高点"):用rateDownSample的max/min采样,配合dimension指定参与比较的数值字段。

6.2 参数速查表

参数默认值适用函数说明
rate5两者采样比例。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.

项目地址:https://gitcode.com/gh_mirrors/f2/F2
点击查看免费下载

相关推荐

上一篇:XposedRimetHelper钉钉助手:远程打卡与位置模拟全解析
下一篇:corvu vs 其他SolidJS组件库:为什么这款无样式UI原语能脱颖而出?

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 7:52:12

Java数据结构实战包:可调试、可测试、可面试的可执行代码库

简介&#xff1a;本资源是一套面向Java初学者与进阶开发者的数据结构与算法系统学习包&#xff0c;聚焦Java语言实现&#xff0c;覆盖面试准备、课程学习与项目实践三大场景。压缩包共140个文件&#xff0c;含48个可读Java源码、80个编译后class文件&#xff0c;辅以PPTX课件、…

作者头像 李华
网站建设 2026/9/26 7:51:15

非靶标代谢组学如何构建表型-代谢-机制证据链发高分文章

做非靶标代谢组学这几年&#xff0c;我最大的感受是&#xff1a;组学数据本身不值钱&#xff0c;值钱的是你怎么把“代谢物的变化”和“生物体的变化”串成一条能讲通的故事。很多课题组拿到样品就往检测平台一送&#xff0c;回来就是厚厚一本“检测报告”——几千个代谢物、几…

作者头像 李华
网站建设 2026/9/26 7:50:52

Deep Agents:生产级Agent工程化落地实践指南

1. 为什么“Deep Agents”不是新框架&#xff0c;而是Agent工程的临界点信号 最近翻完 deep-agents 这个 GitHub 仓库的源码&#xff08;v0.4.2&#xff09;&#xff0c;我坐在工位上盯着终端里跑起来的 agent.execute({"query": "查一下今天北京天气"}…

作者头像 李华
网站建设 2026/9/26 7:49:14

业务可观测性实战:从日志规范到链路追踪的落地指南

1. 可观测性不是运维的专利&#xff0c;而是业务开发的救命稻草先说个我自己的真实感受。做业务开发的人&#xff0c;绝大多数时间都在跟业务逻辑、产品需求、CRUD打交道&#xff0c;可观测性这个词听起来像是SRE、基础架构团队才需要操心的事情。但等到线上真的出了事故&#…

作者头像 李华
网站建设 2026/9/26 7:49:06

不用 Unity,用 Prowl 继续 C# 游戏开发:架构解析与避坑指南

去年有段时间&#xff0c;我一直在琢磨“如果不用Unity&#xff0c;C#开发者还能用什么”这个问题。起因是手头有个做了大半年的独立项目&#xff0c;代码量和资产量上来之后&#xff0c;商业引擎的授权波动、闭源代码、黑盒问题越来越让人心里没底。这时我看到了 Prowl 这个名…

作者头像 李华
网站建设 2026/9/26 7:47:59

寒假集训高效打法:目标拆解、节奏卡点与复盘迁移

2026.2.24&#xff0c;是我们这期寒假集训的最后一天。上午做完结营测评&#xff0c;下午一多半人已经开始打包行李&#xff0c;我坐在教室最后排&#xff0c;把这十来天的流程从头到尾捋了一遍。说实话&#xff0c;真正让集训有效的&#xff0c;根本不是题目量&#xff0c;也不…

作者头像 李华