您现在的位置是:网站首页 > 数据故事讲述文章详情
数据故事讲述
陈川
【
ECharts
】
63952人已围观
8064字
数据故事讲述的核心要素
数据故事讲述是将枯燥的数字转化为引人入胜的叙事过程。它不仅仅是展示数据,而是通过视觉元素和叙事结构,让数据产生意义和影响力。好的数据故事需要三个关键要素:清晰的数据、恰当的视觉呈现和连贯的叙事逻辑。
在ECharts中实现数据故事讲述,首先要理解数据本身。例如,某电商平台2023年季度销售数据:
const salesData = [
{ quarter: 'Q1', sales: 1250, growth: 0.12 },
{ quarter: 'Q2', sales: 1890, growth: 0.23 },
{ quarter: 'Q3', sales: 2100, growth: 0.31 },
{ quarter: 'Q4', sales: 2850, growth: 0.42 }
];
ECharts可视化基础
ECharts提供了丰富的图表类型来支持数据叙事。柱状图适合展示离散数据的对比,折线图能清晰呈现趋势变化,饼图则擅长表现构成比例。
一个基本的柱状图配置示例:
option = {
title: {
text: '2023季度销售额'
},
tooltip: {},
xAxis: {
data: salesData.map(item => item.quarter)
},
yAxis: {},
series: [{
name: '销售额',
type: 'bar',
data: salesData.map(item => item.sales)
}]
};
这个简单图表已经能讲述"第四季度销售额显著增长"的故事,但还缺乏深度。
增强数据叙事的技巧
多维度数据展示
通过添加辅助线或第二y轴,可以同时展示销售额和增长率:
option = {
// ...其他配置
series: [
{
name: '销售额',
type: 'bar',
yAxisIndex: 0,
data: salesData.map(item => item.sales)
},
{
name: '增长率',
type: 'line',
yAxisIndex: 1,
data: salesData.map(item => item.growth)
}
],
yAxis: [
{
type: 'value',
name: '销售额(万元)'
},
{
type: 'value',
name: '增长率',
axisLabel: {
formatter: '{value}%'
}
}
]
};
交互式叙事
ECharts的交互功能可以让用户探索数据故事:
option = {
// ...其他配置
tooltip: {
trigger: 'axis',
formatter: params => {
const data = salesData[params[0].dataIndex];
return `
${data.quarter}<br/>
销售额: ${data.sales}万元<br/>
增长率: ${(data.growth*100).toFixed(1)}%<br/>
${data.growth > 0.3 ? '表现优异!' : ''}
`;
}
},
dataZoom: [{
type: 'slider',
start: 0,
end: 100
}]
};
高级叙事技巧
时间轴动画
对于时间序列数据,使用时间轴可以增强叙事效果:
const yearlyData = [
{ year: '2020', sales: [980, 1200, 1350, 1600] },
{ year: '2021', sales: [1100, 1450, 1680, 1950] },
{ year: '2022', sales: [1150, 1650, 1850, 2200] },
{ year: '2023', sales: [1250, 1890, 2100, 2850] }
];
option = {
baseOption: {
timeline: {
axisType: 'category',
data: yearlyData.map(item => item.year)
},
// ...其他公共配置
},
options: yearlyData.map(data => ({
title: { text: `${data.year}年季度销售额` },
series: [{
data: data.sales
}]
}))
};
自定义主题与样式
通过视觉样式强化叙事重点:
option = {
// ...其他配置
visualMap: {
type: 'piecewise',
pieces: [
{ gt: 2000, label: '优秀表现', color: '#c23531' },
{ gt: 1500, lte: 2000, label: '良好表现', color: '#2f4554' },
{ lte: 1500, label: '一般表现', color: '#61a0a8' }
],
seriesIndex: 0
},
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
};
复杂数据关系的叙事
关系型数据可视化
当需要展示数据间关系时,关系图是很好的选择:
const productRelations = {
nodes: [
{ name: '智能手机', category: 0 },
{ name: '智能手表', category: 0 },
{ name: '耳机', category: 0 },
{ name: '25-35岁', category: 1 },
{ name: '35-45岁', category: 1 }
],
links: [
{ source: '智能手机', target: '25-35岁', value: 58 },
{ source: '智能手表', target: '25-35岁', value: 32 },
{ source: '耳机', target: '25-35岁', value: 45 },
{ source: '智能手机', target: '35-45岁', value: 42 },
{ source: '智能手表', target: '35-45岁', value: 28 },
{ source: '耳机', target: '35-45岁', value: 31 }
]
};
option = {
legend: {
data: ['产品', '用户群体']
},
series: [{
type: 'graph',
layout: 'force',
data: productRelations.nodes.map(node => ({
...node,
symbolSize: node.category === 0 ? 40 : 50,
itemStyle: {
color: node.category === 0 ? '#4f81bd' : '#c0504d'
}
})),
links: productRelations.links,
categories: [
{ name: '产品' },
{ name: '用户群体' }
],
force: {
repulsion: 100,
edgeLength: 100
},
label: {
show: true
},
edgeLabel: {
show: true,
formatter: '{c}%'
}
}]
};
地理数据叙事
地理数据可视化能讲述空间维度的故事:
option = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} (万元)'
},
visualMap: {
min: 800,
max: 5000,
text: ['高', '低'],
realtime: false,
calculable: true,
inRange: {
color: ['#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695']
}
},
series: [{
name: '区域销售',
type: 'map',
map: 'china',
emphasis: {
label: {
show: true
}
},
data: [
{ name: '北京', value: 4830 },
{ name: '上海', value: 5120 },
{ name: '广东', value: 4980 },
{ name: '浙江', value: 3850 },
{ name: '江苏', value: 3620 }
]
}]
};
数据叙事的动态演进
数据更新动画
动态数据更新可以讲述数据变化的故事:
function updateChart() {
const newData = salesData.map(item => ({
...item,
sales: item.sales * (1 + Math.random() * 0.2 - 0.1)
}));
myChart.setOption({
series: [{
data: newData.map(item => item.sales)
}]
});
setTimeout(updateChart, 2000);
}
用户引导式叙事
通过高亮和标注引导用户关注重点:
option = {
// ...其他配置
graphic: [{
type: 'text',
left: 'center',
top: 'bottom',
style: {
text: '第四季度销售额突破历史记录!',
fill: '#c23531',
fontSize: 16
}
}],
markPoint: {
data: [{
type: 'max',
name: '最大值'
}]
},
markLine: {
data: [{
type: 'average',
name: '平均值'
}]
}
};
数据叙事的最佳实践
保持简洁与专注
避免过度装饰,确保每个视觉元素都有叙事目的。比较以下两种方式:
// 过度装饰的配置
option = {
// ...其他配置
series: [{
type: 'pie',
radius: ['40%', '70%'],
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
},
label: {
show: true,
formatter: '{b}: {c} ({d}%)',
color: '#333',
fontSize: 14,
fontWeight: 'bold'
},
emphasis: {
itemStyle: {
shadowBlur: 20,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.8)'
}
}
}]
};
// 简洁有效的配置
option = {
// ...其他配置
series: [{
type: 'pie',
radius: '70%',
label: {
formatter: '{b}: {c}'
},
emphasis: {
scale: true,
scaleSize: 10
}
}]
};
响应式设计
确保数据故事在不同设备上都能有效传达:
function resizeChart() {
const width = window.innerWidth;
const fontSize = width < 600 ? 12 : 14;
myChart.setOption({
textStyle: {
fontSize: fontSize
},
grid: {
top: width < 600 ? '15%' : '20%',
right: width < 600 ? '5%' : '10%',
bottom: width < 600 ? '15%' : '10%',
left: width < 600 ? '15%' : '10%'
}
});
}
window.addEventListener('resize', resizeChart);
数据叙事中的常见误区
误导性视觉呈现
避免可能产生误导的图表配置:
// 误导性的y轴起点
option = {
yAxis: {
type: 'value',
min: 1000 // 当数据范围是1200-3000时,这会夸大差异
}
};
// 更合适的配置
option = {
yAxis: {
type: 'value',
min: 'dataMin' // 根据数据自动确定最小值
}
};
信息过载
避免在同一图表中塞入过多信息:
// 信息过载的配置
option = {
dataset: {
source: [
['product', '2018', '2019', '2020', '2021', '2022', '2023'],
['手机', 1200, 1350, 1420, 1580, 1720, 1850],
['电脑', 800, 920, 1050, 1120, 1250, 1380],
['平板', 450, 520, 580, 620, 680, 750],
['耳机', 320, 380, 420, 480, 520, 580],
['手表', 280, 310, 350, 420, 480, 520]
]
},
xAxis: { type: 'category' },
yAxis: {},
series: [
{ type: 'bar' },
{ type: 'bar' },
{ type: 'bar' },
{ type: 'bar' },
{ type: 'bar' }
]
};
// 更清晰的替代方案
option = {
dataset: {
dimensions: ['product', '2023', '2022'],
source: [
['手机', 1850, 1720],
['电脑', 1380, 1250],
['平板', 750, 680],
['耳机', 580, 520],
['手表', 520, 480]
]
},
xAxis: { type: 'category' },
yAxis: {},
series: [
{
type: 'bar',
encode: { x: 'product', y: '2023' },
name: '2023'
},
{
type: 'bar',
encode: { x: 'product', y: '2022' },
name: '2022'
}
]
};