2019 年做监控产品时,我交付过一张“看起来非常正常”的折线图。接口返回、代码执行和图表渲染都没有报错,产品同学却发现某天设备没有上报,曲线反而平滑地从前一天连到了后一天,像是业务稳定增长。代码没有坏,图表达错了意思。
那次之后我不再把接口数组直接塞给图表库。原始记录要经过字段校验、时间与单位归一、业务聚合、缺失值策略和视觉编码。任何一步默认处理,都可能改变用户读到的结论。
图 1:图表配置只是最后一层,前面的数据语义决定它到底在说什么。
先保留“缺失”和“零”的区别
设备没有上报是 missing,设备上报数量为零是 0。如果前端用 value || 0 统一处理,两种事实会被抹平:
type Sample = {
timestamp: number;
value: number | null;
quality: "reported" | "missing" | "estimated";
};
function normalize(raw: unknown): Sample {
const record = raw as Record<string, unknown>;
const timestamp = Date.parse(String(record.time));
if (!Number.isFinite(timestamp)) throw new Error(`invalid time: ${record.time}`);
if (record.value === null || record.value === undefined) {
return { timestamp, value: null, quality: "missing" };
}
const value = Number(record.value);
if (!Number.isFinite(value)) throw new Error(`invalid value: ${record.value}`);
return { timestamp, value, quality: "reported" };
}缺失点在折线中应该断开、标灰或明确标注,而不是自动补零或连线。是否插值属于业务决定,不能让图表库默认值替团队做。
聚合口径要跟数据一起走
同一个“日活”可能按自然日、过去 24 小时或用户时区计算。只传 {time, value},前端无法解释它。我们后来让查询结果携带口径:
type MetricSeries = {
metric: "active_devices";
unit: "count";
timezone: "Asia/Shanghai";
interval: "1h" | "1d";
aggregation: "distinct_device";
samples: Sample[];
};Tooltip、导出文件和图表标题都从这份元数据生成,避免页面上写“日活”,接口实际返回小时峰值。
一张图需要明确的质量门禁
渲染前我会检查:时间是否单调;重复时间点如何合并;单位是否一致;异常值是否超出可接受范围;数据点数量是否超过前端预算。
| 检查 | 失败处理 |
|---|---|
| 时间戳无法解析 | 拒绝该记录并上报字段样例 |
| 同一时间点重复 | 按明确聚合规则合并,不能只取最后一条 |
| 单位不一致 | 在数据层统一换算,图表层不猜 |
| 缺失率超过阈值 | 图上展示数据质量提示 |
| 点数超过预算 | 服务端降采样或限制时间范围 |
“尽量画出来”在监控产品里不一定友好。一张静默错误的图,比明确告诉用户数据不完整更危险。
视觉编码也要接受评审
我曾把多条趋势线自动分配成十几种相近颜色,在自己的显示器上还能区分,投影后几乎一样。后来视觉映射也进入配置和测试:关键系列颜色固定;状态颜色有稳定语义;颜色之外再用线型或标记区分;Tooltip 保留原始值和单位。
const chartSpec = {
x: { field: "timestamp", type: "time" },
y: { field: "value", unit: series.unit, zeroBaseline: false },
line: { connectNulls: false },
quality: { field: "quality", missingStyle: "gap" },
};zeroBaseline: false 也不能无条件使用。折线图为了观察细小波动可以截断纵轴,柱状图截断基线却会夸大差异。图表类型本身携带表达规则。
给数据管线留可检查的中间结果
排查图表问题时,如果只有最终 option,很难判断错误来自接口、转换还是渲染。我会在开发模式保留每一阶段摘要:原始记录数、过滤数、缺失率、聚合后点数和最终区间。生产日志只记录统计,不上传敏感明细。
这次“没有报错的错误”让我改变了对可视化的理解。图表不是把数值映射成像素的组件,而是一条解释数据的管线。工程师不仅要保证它能画,还要能回答每个点从哪里来、经过什么规则、为什么以这种方式出现。