A

认识 LTTB

2025-05-28 22:30

“真正的问题,往往藏在你以为的理所当然之外。”

在这个项目里,我第一次正面面对了 十几万条数据同时渲染到首页 的挑战。

起点:Tick 数据拖垮首页性能

我们正在开发一款外汇类 App,基于 React Native 构建。首页是一个信息密集的长列表,用 FlatList 渲染各种模块和卡片,大多数情况下表现良好。

但其中有一项叫做「Tick 交易图表」的组件,用来展示某个交易品种的 Tick 数据。这类数据是 毫秒级的交易记录,数量少时只有几十条,多时能达到十几万条

后端坚持:“必须是原始数据,不能裁剪。”
(我猜,也许是因为懒)

结果很明显:
只要图表涉及的数据达到几千条以上,哪怕组件设计再轻量,滚动时仍然明显卡顿;滑动过程中 FPS 降低、页面响应延迟,体验极差。

我尝试了很多优化手段:

  • 图表懒加载、局部渲染
  • 限制图表首屏渲染条数
  • 组件虚拟化、延迟绘制

虽然缓解了一些问题,但没有解决根本矛盾 —— 数据量太大,渲染压力太重。

转折点:偶然遇见 LTTB

在一次搜索 “如何高效展示大规模时间序列数据” 的过程中,我偶然看到一个关键词:
LTTB(Largest-Triangle-Three-Buckets)算法。 我点进去看了一下,这东西的原理立刻让我眼前一亮。

LTTB 是什么?

LTTB 是一种为可视化而设计的下采样算法,它在不破坏数据趋势的前提下,将大量点压缩为少量代表性点

核心思想如下:

  1. 保留原始数据的首尾点;
  2. 把中间的数据分成 N 个桶(bucket);
  3. 每个桶中选出一个最“关键”的点,依据是它与前一个和后一个桶代表点形成的三角形面积最大

因此,LTTB 不简单平均、也不等间隔丢弃,而是保留那些最能反映走势变化的数据点。

比如你原始数据有 100,000 条,LTTB 可以压缩为 200 条,但这 200 条会最大程度保留原数据的趋势。

实践:在 Tick 图中应用 LTTB

了解原理后,我立刻尝试将其应用到我们的问题中。考虑到已有的开源实现大多功能冗余或不够灵活,我决定基于原理自己实现一个精简版本。

原始 Tick 数据结构如下:

TS
[{ time: 1716892458000, ask: 1.0765, bid: 1.0765 }, ...]

为了配合图表组件(如 victory-native),我在渲染前对数据进行 LTTB 压缩:

TS
import lttb from 'js-lttb' const lttbData = data.dukaTicks.length > 200 ? lttb(data.dukaTicks, 200, 'time', 'ask') // 从十几万压缩到 200 : data.dukaTicks;

压缩之后,传给图表组件的数据从十几万条缩减到了几百条,页面响应速度和滑动流畅度显著提升,而视觉趋势几乎没有肉眼可见的损失。

附上照片
图片加载中...
tick图片

成果沉淀:发布 js-lttb

在项目落地后,我将这个算法封装成一个轻量级的 NPM 包,发布为:

👉 js-lttb on npm

特点:

  • 零依赖,纯函数实现;
  • 支持任意结构数据,自定义 xy 映射字段;
  • 支持时间戳、数字等多类型数据;
  • 体积极小(压缩后 < 2KB),非常适合前端场景;

安装使用:

BASH
npm install js-lttb
TS
import lttb from 'js-lttb' const simplified = lttb(data, 500, 'timestamp', 'value')

写在最后

这个过程让我意识到一个重要的认知误区:

前端并不只是堆 UI、调样式,算法思维同样重要。

很多性能瓶颈表面看是“组件卡”、“页面慢”,但真正的问题可能在于数据处理方式 —— 特别是在与后端无法妥协的前提下,如何用前端手段“聪明”地解决问题,就成了关键。

并不是所有性能问题都得靠“强制压缩”或者“懒加载”来处理。

算法的力量,有时候就藏在我们不知道的角落里,也许这正是“成长”的一部分吧 —— 去找到那些我们之前从未听过,却正是所需的答案。


如果你也遇到了 Tick 数据图表相关的性能问题,欢迎试试 js-lttb。如果有改进建议或想法,也欢迎交流与分享。