数据可视化基础
什么是数据可视化
数据可视化是通过图形化手段,将数据转换为可视元素(位置、长度、颜色、形状等),利用人类视觉系统的高效感知能力来理解数据中的模式、趋势和异常。
可视化的核心概念
数据类型
| 数据类型 | 说明 | 示例 | 适合图表 |
|---|---|---|---|
| 定量数据(Quantitative) | 可度量的数值 | 温度、价格、销量 | 折线图、柱状图、散点图 |
| 分类数据(Categorical) | 离散的类别 | 性别、地区、产品类型 | 柱状图、饼图、环形图 |
| 时间数据(Temporal) | 时间序列 | 日期、时间戳 | 折线图、面积图、甘特图 |
| 地理数据(Geographical) | 地理坐标 | 经纬度、行政区 | 地图、热力图 |
| 关系数据(Relational) | 节点与边 | 社交网络、组织架构 | 力导向图、桑基图 |
| 层级数据(Hierarchical) | 树形结构 | 文件系统、分类目录 | 树图、旭日图 |
视觉编码(Visual Encoding)
视觉编码是将数据属性映射到视觉通道(Visual Channel)的过程,是可视化设计的核心:
视觉通道的精确度排序(由高到低):
| 排名 | 定量数据 | 分类数据 |
|---|---|---|
| 1 | 位置(最精确) | 位置 |
| 2 | 长度 | 颜色色相 |
| 3 | 角度/斜率 | 形状 |
| 4 | 面积 | 纹理 |
| 5 | 颜色饱和度/明度 | 尺寸 |
设计原则
- 定量数据优先用位置和长度编码(如柱状图、折线图)
- 分类数据优先用颜色色相和形状编码
- 避免用面积和角度表示精确数值(人类感知不准确)
图表类型选择
| 场景 | 推荐图表 | 避免使用 |
|---|---|---|
| 比较各类别数值 | 柱状图、条形图 | 饼图(>5 类) |
| 展示时间趋势 | 折线图、面积图 | 柱状图(数据点多时) |
| 展示部分占整体比例 | 饼图(≤5 类)、堆叠图 | 3D 饼图 |
| 展示两变量关系 | 散点图 | 折线图(非时序) |
| 展示数据分布 | 直方图、箱线图 | 柱状图 |
| 展示层级关系 | 树图、旭日图 | 柱状图 |
| 展示地理数据 | 地图、热力图 | 柱状图 |
数据处理与转换
可视化前通常需要对原始数据进行预处理:
// 常见的数据转换操作
// 1. 分组聚合
interface SalesRecord {
category: string;
amount: number;
date: string;
}
function groupBy<T>(data: T[], key: keyof T): Map<string, T[]> {
return data.reduce((map, item) => {
const groupKey = String(item[key]);
const group = map.get(groupKey) || [];
group.push(item);
map.set(groupKey, group);
return map;
}, new Map<string, T[]>());
}
// 2. 数据归一化 (Min-Max Normalization) — 将值映射到 [0, 1] 区间
function normalize(values: number[]): number[] {
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min;
if (range === 0) return values.map(() => 0.5);
return values.map((v) => (v - min) / range);
}
// 3. 数据分箱(Binning)— 将连续数据离散化为区间
function bin(values: number[], binCount: number): { range: string; count: number }[] {
const min = Math.min(...values);
const max = Math.max(...values);
const binWidth = (max - min) / binCount;
const bins = Array.from({ length: binCount }, (_, i) => ({
range: `${(min + i * binWidth).toFixed(1)} - ${(min + (i + 1) * binWidth).toFixed(1)}`,
count: 0,
}));
values.forEach((v) => {
const index = Math.min(Math.floor((v - min) / binWidth), binCount - 1);
bins[index].count++;
});
return bins;
}
比例尺(Scale)
比例尺是可视化中最重要的概念之一,它建立了数据空间到视觉空间的映射关系:
// 线性比例尺:等比例映射数值到像素
function linearScale(
domain: [number, number], // 数据范围 [最小值, 最大值]
range: [number, number] // 视觉范围 [起始像素, 结束像素]
): (value: number) => number {
const [d0, d1] = domain;
const [r0, r1] = range;
const ratio = (r1 - r0) / (d1 - d0);
return (value: number) => r0 + (value - d0) * ratio;
}
// 对数比例尺:适合数据范围跨越多个数量级(收入分布、地震强度等)
function logScale(
domain: [number, number],
range: [number, number]
): (value: number) => number {
const [d0, d1] = domain;
const [r0, r1] = range;
const logD0 = Math.log10(d0);
const logD1 = Math.log10(d1);
return (value: number) => {
const t = (Math.log10(value) - logD0) / (logD1 - logD0);
return r0 + t * (r1 - r0);
};
}
// 序数比例尺:将离散类别映射到离散位置
function ordinalScale<T>(domain: string[], range: T[]): (value: string) => T | undefined {
const map = new Map(domain.map((d, i) => [d, range[i % range.length]]));
return (value: string) => map.get(value);
}
// 使用示例
const xScale = linearScale([0, 100], [0, 800]); // 数据 0-100 → 像素 0-800
console.log(xScale(50)); // 400
const colorScale = ordinalScale(
['Apple', 'Google', 'Microsoft'],
['#ff6384', '#36a2eb', '#ffce56']
);
console.log(colorScale('Apple')); // '#ff6384'
色彩理论
配色方案类型
| 类型 | 场景 | 示例 |
|---|---|---|
| 连续色(Sequential) | 单一数值从低到高 | 热力图、密度图 |
| 发散色(Diverging) | 有中心值的正负数据 | 温差图、涨跌幅 |
| 分类色(Categorical) | 不同类别 | 多系列折线图、饼图 |
色彩无障碍
色盲友好
- 约 8% 的男性有色觉障碍
- 避免仅用红绿区分数据
- 同时使用颜色 + 形状/纹理/标签
- 工具:Color Oracle、Chrome DevTools 色觉模拟
// 色盲友好的分类色板
const colorBlindSafePalette = [
'#0072B2', // 蓝色
'#E69F00', // 橙色
'#009E73', // 绿色
'#F0E442', // 黄色
'#56B4E9', // 浅蓝
'#D55E00', // 深橙
'#CC79A7', // 粉色
'#000000', // 黑色
];
前端可视化技术栈
| 技术 | 层级 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| SVG | 底层 | DOM 事件、无损缩放 | 大数据量性能差 | 交互式图表、图标 |
| Canvas 2D | 底层 | 高性能、像素操作 | 无 DOM 事件 | 大数据量、动画 |
| WebGL | 底层 | GPU 加速、3D | 学习曲线高 | 3D 可视化、超大数据 |
| D3.js | 库 | 灵活性极高 | 学习曲线高 | 定制化图表 |
| ECharts | 库 | 开箱即用、功能丰富 | 定制化受限 | 快速开发 |
| Three.js | 库 | 强大的 3D 能力 | 包体积大 | 3D 可视化 |
| Recharts | React 组件 | 声明式、易上手 | 功能有限 | React 项目 |
| AntV | 套件 | G2/G6/L7 生态完整 | 配置复杂 | 蚂蚁系 |
可视化设计原则
1. 数据墨水比(Data-Ink Ratio)
Edward Tufte 提出的概念:最大化数据墨水比,去除非数据元素。
图表中用于展示数据的"墨水"占总"墨水"的比例应尽可能高。
❌ 反面示例:3D 饼图、过度装饰、网格线过多 ✅ 正面示例:简洁的柱状图、清晰的标签
2. 格式塔原则
| 原则 | 说明 | 可视化应用 |
|---|---|---|
| 临近性 | 距离近的元素被视为一组 | 分组柱状图 |
| 相似性 | 相似的元素被视为一组 | 颜色编码 |
| 封闭性 | 倾向看到封闭形状 | 面积图 |
| 连续性 | 倾向沿着曲线/直线看 | 折线图 |
| 图底关系 | 区分前景与背景 | 高亮/灰化 |
3. 常见可视化陷阱
避免这些错误
- 截断 Y 轴:不从 0 开始,夸大差异
- 双 Y 轴误导:两个 Y 轴刻度不同,暗示不存在的相关性
- 3D 效果:透视导致面积/角度失真
- 饼图滥用:超过 5 个类别难以比较
- 彩虹色:色盲不友好,且无法表示有序数据
常见面试问题
Q1: SVG 和 Canvas 如何选择?
答案:
| 维度 | SVG | Canvas |
|---|---|---|
| 渲染方式 | 保留模式(DOM 树) | 即时模式(像素绘制) |
| 事件处理 | 原生 DOM 事件 | 需手动实现命中检测 |
| 性能 | 元素 < 1000 | 可渲染数万图形 |
| 缩放 | 无损缩放(矢量) | 放大失真 |
| 动画 | CSS/SMIL/WAAPI | requestAnimationFrame |
| 可访问性 | 支持(ARIA、title) | 不支持 |
| 适用场景 | 图标、简单图表、交互多 | 大数据量、动画、游戏 |
选择建议:
- 数据量 < 1000、需要丰富交互 → SVG
- 数据量 > 1000、频繁动画更新 → Canvas
- 3D 场景、超大数据量 → WebGL
- 详见 Canvas 与 SVG
Q2: 什么是视觉编码?如何选择合适的视觉通道?
答案:
视觉编码是将数据维度映射到图形的视觉属性。选择原则:
- 精确度优先:定量数据用位置 > 长度 > 角度 > 面积
- 类别区分:分类数据用颜色色相 > 形状 > 纹理
- 认知负荷:同时使用的视觉通道不宜超过 3-4 个
- 冗余编码:重要信息用多种通道同时编码(如颜色+标签)
Q3: 如何处理可视化中的大数据量?
答案:
- 数据层:聚合(分箱、采样)、降维、LOD(层次细节)
- 渲染层:Canvas/WebGL 替代 SVG、离屏渲染、Web Worker 数据处理
- 交互层:渐进式加载、视窗内渲染、交互时降低精度
- 详见 大数据量渲染优化
Q4: 可视化中的色彩设计要注意什么?
答案:
- 语义一致:红色=危险/下跌、绿色=安全/上涨(注意中西文化差异)
- 色盲友好:避免红绿对比,使用蓝橙等安全色对
- 对比度:背景与前景对比度 ≥ 4.5:1(WCAG AA)
- 数量限制:分类色 ≤ 8 种,太多颜色无法区分
- 连续色:单色相渐变(浅蓝→深蓝)而非彩虹色
Q5: 什么是数据墨水比?为什么重要?
答案:
数据墨水比 = 用于展示数据的图形元素 / 图表中所有图形元素。Tufte 主张最大化此比例,移除不传达信息的装饰元素(如 3D 效果、多余网格线、装饰性背景)。在实际开发中,这意味着:删除不必要的网格线、简化图例、去掉 3D 效果、让数据成为视觉焦点。
Q6: 前端可视化技术栈如何选型?
答案:
Q7: 解释比例尺的概念和类型
答案:
比例尺建立了数据空间到视觉空间的映射。常用类型:
- 线性比例尺:数值等比映射,最常用(柱状图高度、折线图坐标)
- 对数比例尺:跨数量级数据(股价、地震强度)
- 序数比例尺:类别 → 颜色/位置
- 时间比例尺:日期 → 像素位置
- 幂比例尺:面积映射(气泡图,面积 ∝ 数据²)
Q8: 如何保证可视化的可访问性?
答案:
- 不仅靠颜色:同时提供形状、纹理、标签
- 文字替代:图表添加
aria-label、<title>、<desc> - 键盘导航:图表支持 Tab 键和方向键操作
- 对比度:前景色与背景色对比度 ≥ 4.5:1
- 数据表格:提供图表数据的文本表格替代
- 屏幕阅读器:SVG 添加 role="img" 和 aria 属性