
数据可视化的质量,不取决于用了多酷的图表库,而取决于视觉编码是否与数据的属性、读者的任务相匹配。一张色彩斑斓但读者找不到答案的图,比一张朴素的条形图更糟。设计的第一原则是:先问读者要完成什么判断,再决定用哪种视觉通道承载数据。位置和长度是人类感知最精确的编码,角度和面积则差得多——这就是为什么饼图在比较相近数值时几乎总是错误选择,而条形图在排序和比较上几乎总是正确选择。
感知实验给出的排序稳定而明确:位置 > 长度 > 角度 > 面积 > 颜色明度 > 颜色色相。设计决策应当照此分配任务:需要精确比较的数值用位置或长度;只需区分类别时用颜色;面积只用于表达数量级差异而非精确比例。一个常见的反模式是用气泡面积编码收入,读者却试图从面积反推精确值——这是把低精度通道用于高精度任务。
// D3.js:从数据到视觉编码的显式映射
const data = [
{ region: "华东", revenue: 1280 },
{ region: "华南", revenue: 960 },
{ region: "华北", revenue: 845 },
{ region: "西南", revenue: 420 },
];
const margin = { top: 20, right: 16, bottom: 36, left: 56 };
const width = 640 - margin.left - margin.right;
const height = 360 - margin.top - margin.bottom;
const svg = d3.select("#chart")
.append("svg")
.attr("viewBox", `0 0 640 360`)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// 顺序比例尺:类别用 band,数值用 linear,不要混用
const x = d3.scaleBand()
.domain(data.map(d => d.region))
.range([0, width])
.padding(0.28);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.revenue)])
.nice()
.range([height, 0]);
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x).tickSizeOuter(0));
svg.append("g")
.call(d3.axisLeft(y).ticks(5).tickFormat(d => `${d / 1000}k`));
svg.selectAll("rect")
.data(data)
.join("rect")
.attr("x", d => x(d.region))
.attr("y", d => y(d.revenue))
.attr("width", x.bandwidth())
.attr("height", d => height - y(d.revenue))
.attr("fill", "#4C78A8")
.attr("rx", 3);这段代码的要点不在 API,而在几个设计决策被写死在映射里:数值轴从零开始(domain([0, max])),条形长度直接编码数值;类别轴用 scaleBand 而非 scaleLinear,避免把离散类别误当成连续变量;nice() 让刻度落在人类可读的整数上;tickSizeOuter(0) 去掉类别轴两端多余的小线,减少视觉噪声。
“条形图必须从零开始”被反复强调,原因是条形图的读者通过长度比例做比较,截断基线会让 5% 的差异看起来像 300%。但折线图表达的是趋势和变化率,此时非零基线可能是合理的——前提是轴标签必须显式标明截断,且读者任务确实是看变化方向而非绝对差值。工程上更稳妥的做法是:让截断成为代码里一个需要显式传参的选项,而不是默认行为。
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
# 正确:零基线,长度忠实编码数值
axes[0].bar(["A", "B", "C"], [102, 105, 108])
axes[0].set_ylim(0, 120)
axes[0].set_title("zero baseline: honest length")
# 反模式:截断基线且未标注,5% 差异被放大为视觉巨变
axes[1].bar(["A", "B", "C"], [102, 105, 108])
axes[1].set_ylim(100, 110)
axes[1].set_title("truncated: misleading")
plt.tight_layout()
plt.show()交互设计最常见的错误是“能加的都加上”:悬停提示、缩放、筛选、联动、动画。每增加一个交互,读者就多一个需要学习的操作,设计师就多一个需要维护的状态。判断标准只有一个:这个交互是否帮助读者回答了他本来要回答的问题。悬停显示精确值在读者需要核对数字时有价值;无意义的入场动画则只是延迟了信息获取。工具提示应当直接显示读者需要的字段,而不是把整行原始数据倒出来。
第一,对比度。正文与背景的对比度应达到 WCAG AA 标准(4.5:1),数据标记与背景之间至少 3:1。浅灰网格线是好的,浅灰数据条则不是。第二,直接标注优于图例。图例迫使读者在颜色和图例之间来回扫视,直接把标签放在数据附近能消除这次眼动。第三,降噪。去掉不必要的边框、背景色、网格线、阴影;每一条保留的视觉元素都应当承载信息,否则就是噪声。
数据可视化设计的专业性,体现在每一个视觉通道的选择都有可辩护的理由:为什么用长度而不用面积,为什么轴从零开始,为什么这个交互存在。代码只是把这些理由固化为可复现的映射规则。做到这一点,图表才从“看起来专业”变成“真的能被正确读取”。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。