☰
TinyBase 时间轴实战:ISO 日期字符串自动检测与 Unix 时间戳的 XAxis 显式声明
2026/10/13 2:42:43 网站建设 项目流程
  • 数据库
  • 后端
  • 前端

【免费下载链接】tinybase

A reactive data store & sync engine.

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

本篇技术指南以 TinyBase 的ui-react-dom-charts图表模块为核心,讲解在折线图的 x 轴上使用时间值的两种方式:一种是直接使用 ISO 日期字符串(例如2026-01-01),由图表组件自动检测并推断出时间比例尺(time scale);另一种是使用 Unix 秒级时间戳(例如1767225600),此时数字默认按普通线性数值处理,必须通过显式声明scale="time"的<XAxis>组件告知图表"这些数字是日期"。读完本文,你将掌握时间轴在 TinyBase React 图表中的完整用法:数据准备、组件声明、刻度格式化、timestampUnit归一化原理,以及如何用 CSS/Less 对时间轴图表做主题化定制。

演示概览:两种时间值,两种声明方式

本文对应仓库中的官方演示文档 06_Time_Axes.md,它是ui-react-dom-charts图表组件系列演示(系列入口)中的一环。演示在一个页面里并排渲染两张折线图:

图表数据源 Tablex 轴取值声明方式
.chart-isovisitsday列,值为 ISO 日期字符串无需任何额外声明,LineChart自动推断时间比例尺
.chart-unixorderstime列,值为 Unix 秒级时间戳必须显式使用<XAxis scale="time">并配合timestampUnit、tickFormatter、ticks等属性

核心结论一句话:ISO 字符串可以直接使用,数值型时间戳则需要scale="time"声明,否则图表只会把它们当作普通的线性大数字来绘制。

工程骨架:Import Map 与模块引入

演示采用浏览器原生 ESM + Import Map 方式运行,无需打包器。首先声明 TinyBase、React 及其图表子模块的导入别名:

<script type="importmap"> { "imports": { "tinybase": "https://esm.sh/tinybase@", "tinybase/ui-react": "https://esm.sh/tinybase/ui-react@", "tinybase/ui-react-dom-charts": "https://esm.sh/tinybase/ui-react-dom-charts@", "react": "https://esm.sh/react@", "react/jsx-runtime": "https://esm.sh/react/jsx-runtime@", "react-dom/client": "https://esm.sh/react-dom/client@" } } </script>

代码中需要引入三类依赖:Store 本身、React 绑定层(Provider与useCreateStore)、以及图表组件(LineChart与XAxis):

import React from 'react'; import {createRoot} from 'react-dom/client'; import {createStore} from 'tinybase'; import {Provider, useCreateStore} from 'tinybase/ui-react'; import {LineChart, XAxis} from 'tinybase/ui-react-dom-charts';

若希望从零开始搭建一个可直接编辑的图表应用,官方还提供了 CLI 脚手架:npm create tinybase@latest,选择 Charting app 模板即可(见演示系列索引)。

数据准备:一张表存 ISO 字符串,一张表存 Unix 时间戳

演示在一个 Store 中创建了两张 Table,正好对应两种时间值形态。visits表使用人类可读的 ISO 日期字符串,orders表使用 Unix 秒级时间戳——注意这里的timestamp辅助函数把Date.UTC得到的毫秒值除以 1000,得到的是秒:

const timestamp = (day) => Date.UTC(2026, 0, day) / 1000; const formatDate = (date) => date.toLocaleDateString('en-US', { day: 'numeric', month: 'short', timeZone: 'UTC', }); const App = () => { const store = useCreateStore(() => createStore() .setTable('visits', { d1: {day: '2026-01-01', visits: 42}, d2: {day: '2026-01-02', visits: 57}, d3: {day: '2026-01-05', visits: 64}, d4: {day: '2026-01-08', visits: 81}, }) .setTable('orders', { d1: {time: timestamp(1), orders: 12}, d2: {time: timestamp(2), orders: 18}, d3: {time: timestamp(5), orders: 21}, d4: {time: timestamp(8), orders: 25}, }), ); return ( <Provider store={store}> <Body /> </Provider> ); }; addEventListener('load', () => createRoot(document.body).render(<App />));

两个值得注意的细节:

  • useCreateStore惰性初始化:Store 只在组件首次挂载时创建一次,避免每次渲染都重建数据。
  • formatDate使用 UTC 时区格式化:因为timestamp()基于Date.UTC生成,格式化时固定timeZone: 'UTC'才能与数据源一致,避免本地时区偏移导致日期显示错位。这正是 Unix 时间戳方案需要处理好的细节之一。

渲染图表:自动推断与显式声明

Body组件中并排放置两张LineChart。第一张只传tableId、xCellId、yCellId三个数据绑定属性;第二张除了同样绑定orders表,还以子组件形式传入了<XAxis>,用scale="time"显式声明时间比例尺:

const Body = () => ( <main> <section> <LineChart className="chart chart-iso" tableId="visits" xCellId="day" yCellId="visits" /> </section> <section> <LineChart className="chart chart-unix" tableId="orders" xCellId="time" yCellId="orders" > <XAxis className="axis-unix" scale="time" tickFormatter={formatDate} ticks={[timestamp(1), timestamp(3), timestamp(5), timestamp(7)]} timestampUnit="second" title="Order date" /> </LineChart> </section> </main> );

在 LineChart.tsx 的源码中可以看到,LineChart是CartesianChart与LineSeries的组合封装:LineChart内部先通过useInitialSeriesSummary计算初始序列摘要,再渲染<CartesianChart>,并把传入的children(即这里的<XAxis>)原样传递下去。CartesianChart在 getParsedChildren 中识别子组件类型,XAxis组件本身在 XAxis.tsx 中只是一个带X_AXIS标记的空组件,真正绘制坐标轴的是内部实现components/XAxis.tsx。

XAxis 属性一览

根据类型定义 index.d.ts,时间模式下的XAxisProps完整属性如下:

属性类型说明
classNamestring附加到坐标轴<g>元素的 CSS 类名,用于主题化
titlestring坐标轴标题,绘制在刻度下方居中位置
tickCountnumber期望的刻度数量(仅作目标参考,实际由算法按"好看"原则决定)
scale'time'(必填,时间模式下)声明 x 轴使用时间比例尺;线性模式可为'auto' \| 'category' \| 'linear'
timestampUnit'millisecond' \| 'second'声明数值型时间戳的单位,默认'millisecond'
min/maxnumber \| string \| Date(TimeValue)覆盖 x 轴显示范围
ticksreadonly TimeValue[]指定刻度位置列表,会覆盖自动刻度计算
tickFormatter(tick: Date, timestamp: number) => string自定义刻度标签格式化函数,时间模式下第一个参数是Date对象

注意一个类型约束:LinearXAxisProps中timestampUnit被声明为never,即只有scale="time"时才允许使用timestampUnit;同理,线性模式下的ticks必须是数字数组,而时间模式下的ticks可以是number | string | Date的混合数组。

源码级原理:ISO 字符串如何被自动检测

第一张图之所以"开箱即用",秘密在于 data.ts 中getResolvedXScale的自动推断逻辑:当没有显式指定scale时,如果所有 x 值都是字符串,并且都能通过normalizeTimeValue解析出有限的时间戳,那么比例尺就会被推断为TIME;否则回退为CATEGORY(类别轴)或LINEAR(线性轴)。

ISO 日期识别的关键在于正则ISO_DATE = /^\d{4}-\d{2}-\d{2}(?:$|T)/(见 data.ts):形如2026-01-01或以2026-01-01T开头的字符串即可被识别为日期。normalizeTimeValue(data.ts)统一了三种输入形态:

  1. 数字:按timestampUnit处理——单位是second时乘以 1000 转成毫秒,millisecond时直接使用;
  2. 字符串:若匹配 ISO_DATE 正则,则解析为毫秒时间戳;
  3. Date实例:直接取其毫秒时间戳。

任何解析不出的值都会返回undefined,从而被过滤掉,不会进入图表。

源码级原理:Unix 秒时间戳为什么要scale="time"与timestampUnit="second"

数值型时间戳默认被当作普通数字处理:在 getSeriesSummary 中,所有 x 值均为数字时continuousX会被置为true,比例尺解析结果倾向于LINEAR。此时 17 亿左右的秒级时间戳会被当作巨大线性数值,刻度会落在难以阅读的整数上——这正是第二张图必须显式声明scale="time"的原因。

而一旦scale="time",CartesianChart.tsx 中timestampUnit的解析规则是:xAxis.scale == TIME && xAxis.timestampUnit == 'second'时为SECOND_UNIT,否则一律MILLISECOND。timestampUnit的实际效果贯穿整个流水线:

  • normalizeTimeValue在SECOND_UNIT下把值乘以 1000,归一化到毫秒;
  • getXScaleDomain(data.ts)据此计算时间域[min, max];
  • 刻度生成与标签格式化都基于归一化后的毫秒时间戳工作。

所以timestampUnit="second"本质上是告诉图表"我的数字是秒,请先换算成毫秒再按时间处理"。若省略该属性,秒值会被误当作毫秒,日期将显示为 1970 年附近的错误时间。

刻度自动生成与标签格式化

时间轴刻度并非简单等分,而是由 wilkinson.ts 中的getTimeTicks在预设时间间隔表上搜索最优解。TIME_INTERVALS(wilkinson.ts)定义了从1 秒、5 秒、15 秒、1 分钟、1 小时、1 天、1 周、1 月、1 季、1 年,一直到50 年、100 年的 30 种候选间隔;getTimeTicks对每个间隔生成候选刻度序列,并按照简洁性、覆盖率、密度、标签间距四个权重维度打分(wilkinson.ts),选出最"漂亮"的一组。这也是演示中数据跨度只有 7 天,却能得到 1 天或 2 天间隔刻度、而非粗暴均分的原因。

标签的默认格式化由getTimeTickLabel(wilkinson.ts)完成,它会根据相邻刻度的最小间隔自适应精度:间隔小于 1 分钟时显示到秒(YYYY-MM-DD HH:mm:ss),小于 1 天显示到分钟,小于 1 月只显示日期,小于 1 年显示年月,更大则只显示年份。

演示中提供了自定义的tickFormatter={formatDate},它会完全接管标签渲染。从 components/XAxis.tsx 的实现可以看到:当 x 比例尺为TIME时,getTickLabel会先把刻度归一化为毫秒时间戳,再调用tickFormatter?.(dateNew(timestamp), timestamp)——因此时间模式下自定义格式化函数收到的是Date对象与毫秒时间戳两个参数。同时,示例还通过ticks={[timestamp(1), timestamp(3), timestamp(5), timestamp(7)]}显式指定了 1、3、5、7 号四个刻度位置,让标签与数据点完全对齐。ticks与tickFormatter结合,是最常见的"自定义刻度"组合拳:前者控制刻度摆在哪,后者控制每个刻度显示成什么样。

时间轴的样式定制

两张图表共享大部分样式,仅通过.chart-iso与.chart-unix两套配色区分,用来证明同一套时间轴行为可以完全配合普通的 CSS 定制。完整 Less 如下:

@font-face { font-family: Inter; src: url(https://tinybase.org/fonts/inter.woff2) format('woff2'); } * { box-sizing: border-box; } body { background: white; font-family: Inter, sans-serif; margin: 0; } main { display: grid; gap: 1rem; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 2rem auto; max-width: 64rem; padding: 0 2rem; section { min-width: 0; } } .chart { background: #f7f9fb; border: 1px solid #dce5ee; border-radius: 0.5rem; color: #596779; display: block; font-size: 12px; height: 18rem; padding: 1rem; width: 100%; .grid { color: #d7e0ea; stroke-dasharray: 4 6; } .axes { .title { fill: #1f2937; font-weight: 700; } } .line { stroke-linecap: round; stroke-linejoin: round; stroke-opacity: 0.9; stroke-width: 3; } .points { fill: white; stroke-width: 2; } } .chart-iso { .area { fill: #3b82a0; fill-opacity: 0.12; } .line, .points { stroke: #1f7799; } } .chart-unix { .area { fill: #2b8c67; fill-opacity: 0.12; } .line, .points { stroke: #2b8c67; } .axis-unix { fill: #2f6c4c; } } @media (max-width: 52rem) { main { grid-template-columns: 1fr; padding: 0 1rem; } }

样式中值得关注的 SVG 内部结构约定(对应ui-react-dom-charts的渲染输出,见 CartesianChart.tsx):

  • .grid:虚线网格线,通过stroke-dasharray: 4 6实现点划线效果;
  • .axes .title:坐标轴标题文本,用fill控制颜色;
  • .line/.points:折线与数据点,通过stroke着色;数据点内部填充白色并加描边,形成"空心点"效果;
  • .axis-unix:来自<XAxis className="axis-unix">,CSS 类名会附加到坐标轴<g>元素(见 components/XAxis.tsx),这里用它把第二张图的坐标轴文字染成深绿色,与折线配色呼应;
  • 媒体查询在窄屏(52rem以下)时把双栏 grid 折叠为单栏,保证移动端可读。

小结与选型建议

通过这个演示,可以提炼出 TinyBaseui-react-dom-charts时间轴的三条使用准则:

  1. 数据源为 ISO 日期字符串时零配置:LineChart通过ISO_DATE正则自动推断时间比例尺,直接绑定xCellId即可;
  2. 数据源为数值型时间戳时必须显式声明:在<XAxis>上同时设置scale="time"与timestampUnit(秒还是毫秒),否则会被当作线性大数字;min/max/ticks会参与域的收敛与刻度覆盖(实现见 CartesianChart.tsx);
  3. 刻度与标签可完全自定义:ticks指定刻度位置、tickFormatter接收Date与毫秒时间戳、tickCount调节密度,三者与自动刻度算法(Wilkinson 式评分搜索)互补。

若需深入阅读实现,建议从 common/data.ts(时间值归一化与比例尺推断)、common/wilkinson.ts(时间刻度与标签算法)、components/CartesianChart.tsx(整体装配与属性解析)三个文件入手。

  • 数据库
  • 后端
  • 前端

【免费下载链接】tinybase

A reactive data store & sync engine.

项目地址:https://gitcode.com/gh_mirrors/ti/tinybase
点击查看免费下载
上一篇:八大网盘直链解析终极指南:告别限速,轻松获取真实下载地址
下一篇:终极网盘直链下载助手:告别限速困扰,一键获取九大平台真实下载地址

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询