- 数据可视化
- 前端
【免费下载链接】F2
📱📈An elegant, interactive and flexible charting library for mobile.
导读:本文聚焦 F2(@antv/f2)图表库的数据处理体系,围绕 数据处理教程 系统讲解基本数据格式、特殊图表(饼图、区间柱状图、K 线图、气泡图)的数据结构要求,以及过滤、排序、聚合、转换、动态更新等常见实战操作,并深入 Chart 与 ScaleController 源码揭示数据在渲染管线中的流转原理。读完本文,你将能正确组织各类图表的数据源,并在移动端实现带动画过渡的数据更新。
基本数据格式:JSON 数组 + 标准对象元素
F2 要求数据源为JSON 数组,数组的每个元素是一个标准 JSON 对象。这一约束在类型定义中得到严格体现:在 packages/f2/src/chart/Data.d.ts 中,Data<TRecord>被定义为TRecord[],而DataRecord即Record<string, any>——即一个以字符串为键、任意类型为值的对象。Chart 组件的data属性正是接收该类型的数据。
一个最典型的时间序列数据示例如下:
const data = [ { year: 2010, sales: 40 }, { year: 2011, sales: 30 }, { year: 2012, sales: 50 }, { year: 2013, sales: 60 }, { year: 2014, sales: 70 }, { year: 2015, sales: 80 }, { year: 2016, sales: 80 }, { year: 2017, sales: 90 }, { year: 2018, sales: 120 }, ];将数据传入 Chart 后,通过几何标记组件(如 Line、Point)指定字段映射:
<Canvas context={context}> <Chart data={data}> <Line x="year" y="sales" /> <Point x="year" y="sales" /> <Tooltip /> </Chart> </Canvas>x、y分别指定数据对象中的字段名,F2 会据此为字段建立度量(scale)并完成坐标映射。
数据格式要求
| 要求 | 说明 |
|---|---|
| 数组格式 | 数据源必须是数组 |
| 对象元素 | 数组元素必须是对象 |
| 字段名 | 对象的键作为字段名,用于映射到图表属性 |
| 字段值 | 支持字符串、数字、数组、日期等类型 |
字段值的多样性在底层度量类型推断中体现得很清楚。查看 packages/f2/src/controller/scale.ts 中的_getType逻辑:当未显式指定type时,F2 会根据字段首值自动推断——首值为数字时推断为linear(连续线性度量),否则推断为cat(分类度量);而当字段是数字或值缺失时,则推断为identity(常量度量)。数组、日期等类型则在几何组件层被特殊处理(见下文特殊图表章节)。
特殊图表的数据格式
饼图:必须包含字符串类型的常量字段
绘制饼图时,数据集中的每条记录必须包含一个常量字段(且必须是字符串类型):
const data = [ { name: '芳华', percent: 0.4, a: '1' }, { name: '妖猫传', percent: 0.2, a: '1' }, { name: '机器之血', percent: 0.18, a: '1' }, { name: '心理罪', percent: 0.15, a: '1' }, { name: '寻梦环游记', percent: 0.05, a: '1' }, { name: '其他', percent: 0.02, a: '1' }, ]; <Chart data={data} coord={{ type: 'polar' }}> <Interval x="a" y="percent" color="name" coord="polar" /> </Chart>为什么需要常量字段?饼图使用极坐标系,所有数据需要映射到相同的角度范围。常量字段(如a: '1')确保所有数据点共享相同的角度起始位置。
从源码层面看,字符串常量字段会让_getType将其推断为identity类型(见 scale.ts)。在 packages/f2/src/deps/f2-scale/src/identity/index.ts 中可以看到 identity 度量的核心行为:scale()方法将定义域内的值统一映射到range[0](即角度起始位置),从而让所有扇区从同一角度开始绘制;同时calculateTicks()直接返回常量值列表,保证分类分组只落在这一维上。这就是饼图必须用常量字符串字段把各记录“归一到同一起点”的底层原理。
区间柱状图:字段值为数组时自动映射为区间
当 x 轴或 y 轴的数据为数组时,F2 会自动将其映射为区间,从而绘制区间柱状图(如范围条):
const data = [ { x: '分类一', y: [76, 100] }, { x: '分类二', y: [56, 108] }, { x: '分类三', y: [38, 129] }, { x: '分类四', y: [58, 155] }, { x: '分类五', y: [45, 120] }, { x: '分类六', y: [23, 99] }, { x: '分类七', y: [18, 56] }, { x: '分类八', y: [18, 34] }, ]; <Chart data={data}> <Interval x="x" y="y" /> </Chart>数组表示区间的最小值和最大值:[最小值, 最大值]。在底层,区间数据会展开为yMin/yMax两个边界值,最终由 packages/f2/src/components/interval/view/rect.tsx 渲染为从yMin到yMax的矩形(height: yMax - yMin)。同理,区间数据在堆叠场景下会触发 scale.ts 中的_updateStackRange,对整个区间的min/max做一次遍历求值,确保堆叠后的度量范围完整覆盖所有区间的极值。
股票图(K 线图):四元数组[open, close, lowest, highest]
股票图需要包含开盘价、收盘价、最高价、最低价,使用数组格式:
const data = [ { date: '2023-01', value: [100, 110, 95, 120] }, // [open, close, lowest, highest] { date: '2023-02', value: [110, 105, 100, 115] }, { date: '2023-03', value: [105, 120, 102, 125] }, ]; <Chart data={data}> <Axis field="date" type="timeCat" /> <Candlestick x="date" y="value" /> </Chart>数组格式说明:[open, close, lowest, highest]
open- 开盘价close- 收盘价lowest- 最低价highest- 最高价
在渲染层,packages/f2/src/components/candlestick/candlestickView.tsx 对每个记录做了精细的拆分:y[2]到y[3]绘制连接最高价与最低价的影线(line元素),yMin/yMax绘制开盘价与收盘价之间的实体(rect元素),实体填充色即涨跌颜色。需要留意的是时间轴字段建议配合timeCat类型的时间分类度量,确保 K 线按时间顺序排布(时间分类度量的注册见 scale.ts)。
散点图(气泡图):以额外字段承载大小与颜色维度
散点图可以包含额外的维度(如大小、颜色分组):
const data = [ { x: 10, y: 20, size: 5, category: 'A' }, { x: 15, y: 25, size: 10, category: 'B' }, { x: 20, y: 18, size: 8, category: 'A' }, ]; <Chart data={data}> <Point x="x" y="y" size="size" color="category" /> </Chart>这里x、y决定点的位置,size决定点的大小(映射为气泡半径),color决定颜色分组。数据对象中可自由扩展的键值(如size、category)正体现了DataRecord = Record<string, any>的开放结构——字段名即映射入口,图表属性与数据字段通过名称一一对应。
数据处理:渲染前的数据准备
F2 不限制数据在进入 Chart 之前的形态,你可以用标准 JavaScript 数组方法对数据进行任意加工。
数据过滤
在传递给 Chart 前,可以使用 JavaScript 的数组方法过滤数据:
const rawData = [ { year: 2010, sales: 40, category: 'A' }, { year: 2011, sales: 30, category: 'B' }, { year: 2012, sales: 50, category: 'A' }, { year: 2013, sales: 60, category: 'B' }, ]; // 只保留 category 为 'A' 的数据 const data = rawData.filter(item => item.category === 'A'); <Chart data={data}> <Line x="year" y="sales" /> </Chart>数据排序
const rawData = [ { name: 'A', value: 30 }, { name: 'B', value: 50 }, { name: 'C', value: 20 }, ]; // 按 value 降序排列 const data = [...rawData].sort((a, b) => b.value - a.value); <Chart data={data}> <Interval x="name" y="value" /> </Chart>注意示例中使用[...rawData]先做浅拷贝再排序,避免修改原始数组,这是保持数据源可复用的好习惯。
数据聚合
const rawData = [ { category: 'A', value: 10 }, { category: 'A', value: 20 }, { category: 'B', value: 30 }, { category: 'B', value: 40 }, ]; // 按 category 聚合求和 const aggregated = {}; rawData.forEach(item => { if (!aggregated[item.category]) { aggregated[item.category] = 0; } aggregated[item.category] += item.value; }); const data = Object.entries(aggregated).map(([category, value]) => ({ category, value, })); // 结果: [{ category: 'A', value: 30 }, { category: 'B', value: 70 }]数据转换
const rawData = [ { date: '2023-01-01', value: 100 }, { date: '2023-02-01', value: 120 }, ]; // 转换日期格式 const data = rawData.map(item => ({ ...item, date: new Date(item.date), // 或添加计算字段 valueFormatted: item.value.toFixed(2), }));转换后的Date对象值可交由 F2 的 time 类型度量处理,新增的计算字段(如valueFormatted)则可作为标签、Tooltip 的展示字段使用。
数据更新:通过update触发动画过渡
F2 支持动态更新数据,实现动画过渡效果。其核心机制在于 Chart 在收到新data时,会让度量随数据变化而重建:
let chart = null; // 初始化数据 const data1 = [ { genre: 'Sports', sold: 275 }, { genre: 'Strategy', sold: 115 }, ]; const { props } = ( <Canvas context={context}> <Chart data={data1}> <Interval x="genre" y="sold" /> </Chart> </Canvas> ); chart = new Canvas(props); chart.render(); // 更新数据 const data2 = [ { genre: 'Sports', sold: 350 }, { genre: 'Strategy', sold: 200 }, ]; const { props: newProps } = ( <Canvas context={context}> <Chart data={data2}> <Interval x="genre" y="sold" /> </Chart> </Canvas> ); chart.update(newProps); // 自动触发动画从源码看,Chart 的willReceiveProps生命周期(packages/f2/src/chart/index.tsx)会比对前后data引用:一旦nextData !== lastData,立即调用scale.changeData(nextData)。而 scale.ts 中的changeData会将数据更新到控制器并清空全部 scale 实例缓存,使度量在下次访问时基于新数据重建,同时几何组件带着新旧数据参与 update 动画过渡(如柱状图在 rect.tsx 中通过update动画对x/y/width/height属性做插值)。这正是“改数据自动带动画”的实现原理。
另外需要说明:源码对data为undefined的场景也有兼容处理。Chart 渲染入口会先判断if (!originData) return null(index.tsx),对应的回归测试可见 packages/f2/test/chart/changeDataWithUndefined.test.tsx(“Chart 切换data为undefined”),用于防止数据被切换为undefined后再恢复时图表异常缩小。
常见问题与处理策略
空数据处理
// 数据为空时显示空状态 const data = []; if (data.length === 0) { // 显示空状态提示 return <EmptyState />; } return ( <Chart data={data}> <Interval x="genre" y="sold" /> </Chart> );缺失值处理
const data = [ { year: 2010, sales: 40 }, { year: 2011, sales: null }, // 缺失值 { year: 2012, sales: 50 }, ]; // 过滤掉缺失值 const cleanData = data.filter(item => item.sales != null); // 或使用默认值填充 const filledData = data.map(item => ({ ...item, sales: item.sales ?? 0, }));两种策略适用场景不同:过滤适合“断点即空”的折线(F2 的 line 组件对空值有专门的连接策略,可参考 site/examples/line/other 中 null 数据的示例);填充默认值则适合需要保持数据点完整的场景。注意??只替换null/undefined,不会误伤0等合法值。
大数据量处理
对于大数据量,建议:
- 数据抽样:在前端进行随机抽样
- 数据分页:只加载当前页的数据
- 服务端聚合:在服务端完成聚合计算
// 数据抽样示例 function sampleData(data, maxSize) { if (data.length <= maxSize) return data; const step = Math.ceil(data.length / maxSize); return data.filter((_, index) => index % step === 0); } const largeData = [...]; // 大数据集 const sampledData = sampleData(largeData, 1000); <Chart data={sampledData}> <Line x="date" y="value" /> </Chart>仓库中f2-algorithm包还提供了更专业的降采样方案:packages/f2-algorithm/src/lttbDownSample.ts(LTTB 最优点抽样)与 packages/f2-algorithm/src/rateDownSample.ts(按比例抽样),其正确性由 packages/f2-algorithm/test/sample.test.tsx 中的快照测试验证。当时间序列数据量极大时,LTTB 比等距抽样能更好地保留曲线形态。
数据源类型:静态、API 与用户输入
静态数据
直接定义在代码中的常量数据:
const data = [ { x: 1, y: 2 }, { x: 2, y: 4 }, ];API 数据
从远程 API 获取:
async function fetchData() { const response = await fetch('https://api.example.com/data'); const data = await response.json(); const { props } = ( <Canvas context={context}> <Chart data={data}> <Interval x="category" y="value" /> </Chart> </Canvas> ); const canvas = new Canvas(props); canvas.render(); } fetchData();由于 F2 要求数据源为对象数组,如果 API 返回的结构不是直接可用的对象数组,可在拿到响应后先行转换(如Object.entries展开、字段重命名),再交给 Chart。
用户输入
响应用户交互:
function updateChart(userInput) { const data = processData(userInput); const { props: newProps } = ( <Canvas context={context}> <Chart data={data}> <Interval x="category" y="value" /> </Chart> </Canvas> ); chart.update(newProps); }完整示例:从原始数据到成品图表
将数据转换、度量配置与组件声明组合在一起,即得到一个完整的可运行示例:
import { Canvas, Chart, Interval, Axis, Tooltip } from '@antv/f2'; const rawData = [ { month: '1月', sales: 100, profit: 20 }, { month: '2月', sales: 120, profit: 25 }, { month: '3月', sales: 90, profit: 15 }, { month: '4月', sales: 150, profit: 35 }, { month: '5月', sales: 180, profit: 40 }, { month: '6月', sales: 200, profit: 45 }, ]; // 数据处理:添加利润率字段 const data = rawData.map(item => ({ ...item, profitRate: (item.profit / item.sales * 100).toFixed(2) + '%', })); const context = document.getElementById('container').getContext('2d'); const { props } = ( <Canvas context={context}> <Chart data={data} scale={{ sales: { min: 0, }, }} > <Axis field="month" /> <Axis field="sales" /> <Interval x="month" y="sales" color="month" /> <Tooltip /> </Chart> </Canvas> ); const canvas = new Canvas(props); canvas.render();此示例同时体现了三件事:一是通过map派生计算字段(profitRate);二是通过 Chart 的scale配置对sales字段设置min: 0,确保柱状图从 0 开始(未显式定义时,scale.ts 会基于数据自动计算 min/max 并默认开启nice取整);三是color="month"按月份为柱子分配颜色。
更多示例与相关文档
- 饼图示例
- 区间柱状图示例
- 股票图示例
- 动态数据示例(timeline 相关 demo)
数据字段确定后,进一步深入可阅读:
- 度量(scale)
- 核心概念
- 图形语法
小结
F2 的数据体系可以概括为一条清晰的主线:对象数组进入 Chart → 字段名触发度量建立与推断 → 特殊字段值(常量、数组、Date)驱动特殊几何渲染 → 数据更新时度量重建带动画过渡。掌握基本 JSON 数组格式、四类特殊图表的字段约定(饼图常量字段、区间数组、K 线四元数组、散点附加维度),再配合渲染前的过滤、排序、聚合、转换,以及chart.update()动态更新,即可覆盖移动端图表从静态展示到交互式动态可视化的绝大部分数据场景。
- 数据可视化
- 前端
【免费下载链接】F2
📱📈An elegant, interactive and flexible charting library for mobile.
相关推荐
F2 数据处理完全指南:从基础格式到动态更新的数据驱动图表实战
F2 数据处理完全指南:从基础格式到动态更新的数据驱动图表实战 导读 数据是图表最基础也最关键的部分。本文以 F2 移动端图表库的官方数据处理教程( site/
数据可视化前端F2 移动端图表放大镜 Magnifier 组件实战指南:动态折线图聚焦最新数据
F2 移动端图表放大镜 Magnifier 组件实战指南:动态折线图聚焦最新数据 Magnifier 是 F2 移动端图表库内置的局部放大组件,它通过一个圆形"
数据可视化前端使用 @antv/f2-algorithm 为 F2 移动端图表做高性能数据采样
使用 @antv/f2 algorithm 为 F2 移动端图表做高性能数据采样 数据采样(down sampling / data sampling)是移动端
数据可视化前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考