在数据大屏(数据看板)场景中,常遇到以折线图方式展现数据的同比情况的需求,即在同一张折线图中显示当前周期和上一周期的数据对比情况,如下图所示:

具体的需求包含以下几个方面:
- 在同一张图表中展示2条折线;
Tooltip
分别提示不同周期的同一时间点的数据详情;- 弱化显示上一周期的数据,突出显示当前周期的数据;
- 支持不同时间维度(时间粒度)之间的切换,比如日、周、月、年等之间的切换,横坐标(x轴)和
Tooltip
随之动态切换显示不同的时间标签(label)。
下面针对上述需求分别作一介绍。
在同一张图表中展示2条折线
这个需求比较简单,可以有多种实现方式,直接对照ECharts的文档即可,本文采用dataset
和series
方式。
Tooltip实现
单一的时间维度比较简单,同样参照ECharts文档即可实现,代码如下:
typescriptCopy code- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
tooltip: {
trigger: 'axis',
formatter: (params: DefaultLabelFormatterCallbackParams[]) => {
const param1 = params[0];
const param2 = params[1];
const itemData1 = <StatisticsEntity>param1.data;
const itemData2 = <StatisticsEntity>param2?.data;
const seriesName = `<div>${param1.seriesName}</div>`;
const unit = '';
const value1 = itemData1.count;
const value2 = itemData2?.count;
const title1 = getChartDateTitle(itemData1.rawDate);
const nameStyle = 'style="margin-left: 4px;"';
const valueStyle = 'style="margin-left: 20px; font-weight:bold;"';
const rowStyle = 'style="display: flex; justify-content: space-between;"';
const itemName1 = `${param1.marker}<span ${nameStyle}>${title1}</span>`;
const itemValue1 = `<span ${valueStyle}>${value1}${unit}</span>`;
let tooltip2 = '';
if (param2) {
const title2 = getChartDateTitle(itemData2.rawDate);
const itemName2 = `${param2.marker}<span ${nameStyle}>${title2}</span>`;
const itemValue2 = `<span ${valueStyle}>${value2}${unit}</span>`;
tooltip2 = `<div ${rowStyle}><div>${itemName2}</div>${itemValue2}</div>`;
}
return `${seriesName}<div ${rowStyle}><div>${itemName1}</div>${itemValue1}</div>${tooltip2}`;
}
}
tooltip: {
trigger: 'axis',
formatter: (params: DefaultLabelFormatterCallbackParams[]) => {
const param1 = params[0];
const param2 = params[1];
const itemData1 = param1.data;
const itemData2 = param2?.data;
const seriesName = `${param1.seriesName}`;
const unit = '';
const value1 = itemData1.count;
const value2 = itemData2?.count;
const title1 = getChartDateTitle(itemData1.rawDate);
const nameStyle = 'style="margin-left: 4px;"';
const valueStyle = 'style="margin-left: 20px; font-weight:bold;"';
const rowStyle = 'style="display: flex; justify-content: space-between;"';
const itemName1 = `${param1.marker}${title1}`;
const itemValue1 = `${value1}${unit}`;
let tooltip2 = '';
if (param2) {
const title2 = getChartDateTitle(itemData2.rawDate);
const itemName2 = `${param2.marker}${title2}`;
const itemValue2 = `${value2}${unit}`;
tooltip2 = `${itemName2}${itemValue2}`;
}
return `${seriesName}${itemName1}${itemValue1}${tooltip2}`;
}
}
以上代码的难点在于对ECharts API的熟悉程度。而更为复杂的需求4的动态切换功能,需要对getChartDateTitle
方法进行改造,增加时间维度(DateDimension
)参数,并根据不同的时间维度返回不同的标签值,本文不再赘述。
突出显示当前周期
ECharts默认根据series
定义顺序显示折线图的层级,后定义的在最上面。而文档中有一个配置项可以改变显示层级,即series
的z
值:
typescriptCopy code- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
{
type: 'line',
name: '浏览量(PV)',
encode: {
x: 'date',
y: 'count'
},
datasetId: 'dataset_current',
z: 2,
itemStyle: {
color: '#1890ff'
}
}
{
type: 'line',
name: '浏览量(PV)',
encode: {
x: 'date',
y: 'count'
},
datasetId: 'dataset_current',
z: 2,
itemStyle: {
color: '#1890ff'
}
}
上述代码中,另一个配置项itemStyle
定义折线图的颜色,因此,若需要弱化显示上一周期的数据,将其设为一个较浅的颜色即可。需要注意的是,在CSS中比较常用的透明度方法也同样适用于ECharts,因此,可以直接将color
值定义为rgba
形式的值。
不同时间维度(粒度)之间的切换
原理也不难:获取当前的时间维度(粒度),根据时间维度(粒度)重新计算起止时间后重新发起请求获取对应时间周期的数据,再调用一下setOption
方法即可。需要注意的是,此时需要同时更新X轴和Tooltip
的显示值,并且,后端接口返回的数据中往往缺失没有数据的点,因此还需要手动填补缺失的数据。具体方法本文不再赘述。
总结
至此,折线图的同比展示需求全部实现,以下是完整代码:
typescriptCopy code- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
const option = {
grid: {
left: 50,
right: 8,
top: 12,
bottom: 20,
width: 600,
height: 240
},
xAxis: {
type: 'category'
},
yAxis: {
type: 'value'
},
dataset: [
{
id: 'dataset_current',
source: pvData
},
{
id: 'dataset_last',
source: lastPvData
}
],
series: [
{
type: 'line',
name: '浏览量(PV)',
encode: {
x: 'date',
y: 'count'
},
datasetId: 'dataset_current',
z: 2,
itemStyle: {
color: '#1890ff'
}
},
{
type: 'line',
name: '浏览量(PV)',
encode: {
x: 'date',
y: 'count'
},
datasetId: 'dataset_last',
z: 1,
itemStyle: {
color: '#93c6ff'
}
}
],
tooltip: {
trigger: 'axis',
formatter: (params: DefaultLabelFormatterCallbackParams[]) => {
const param1 = params[0];
const param2 = params[1];
const itemData1 = <StatisticsEntity>param1.data;
const itemData2 = <StatisticsEntity>param2?.data;
const seriesName = `<div>${param1.seriesName}</div>`;
const unit = '';
const value1 = itemData1.count;
const value2 = itemData2?.count;
const title1 = getChartDateTitle(itemData1.rawDate);
const nameStyle = 'style="margin-left: 4px;"';
const valueStyle = 'style="margin-left: 20px; font-weight:bold;"';
const rowStyle = 'style="display: flex; justify-content: space-between;"';
const itemName1 = `${param1.marker}<span ${nameStyle}>${title1}</span>`;
const itemValue1 = `<span ${valueStyle}>${value1}${unit}</span>`;
let tooltip2 = '';
if (param2) {
const title2 = getChartDateTitle(itemData2.rawDate);
const itemName2 = `${param2.marker}<span ${nameStyle}>${title2}</span>`;
const itemValue2 = `<span ${valueStyle}>${value2}${unit}</span>`;
tooltip2 = `<div ${rowStyle}><div>${itemName2}</div>${itemValue2}</div>`;
}
return `${seriesName}<div ${rowStyle}><div>${itemName1}</div>${itemValue1}</div>${tooltip2}`;
}
}
};
const option = {
grid: {
left: 50,
right: 8,
top: 12,
bottom: 20,
width: 600,
height: 240
},
xAxis: {
type: 'category'
},
yAxis: {
type: 'value'
},
dataset: [
{
id: 'dataset_current',
source: pvData
},
{
id: 'dataset_last',
source: lastPvData
}
],
series: [
{
type: 'line',
name: '浏览量(PV)',
encode: {
x: 'date',
y: 'count'
},
datasetId: 'dataset_current',
z: 2,
itemStyle: {
color: '#1890ff'
}
},
{
type: 'line',
name: '浏览量(PV)',
encode: {
x: 'date',
y: 'count'
},
datasetId: 'dataset_last',
z: 1,
itemStyle: {
color: '#93c6ff'
}
}
],
tooltip: {
trigger: 'axis',
formatter: (params: DefaultLabelFormatterCallbackParams[]) => {
const param1 = params[0];
const param2 = params[1];
const itemData1 = param1.data;
const itemData2 = param2?.data;
const seriesName = `${param1.seriesName}`;
const unit = '';
const value1 = itemData1.count;
const value2 = itemData2?.count;
const title1 = getChartDateTitle(itemData1.rawDate);
const nameStyle = 'style="margin-left: 4px;"';
const valueStyle = 'style="margin-left: 20px; font-weight:bold;"';
const rowStyle = 'style="display: flex; justify-content: space-between;"';
const itemName1 = `${param1.marker}${title1}`;
const itemValue1 = `${value1}${unit}`;
let tooltip2 = '';
if (param2) {
const title2 = getChartDateTitle(itemData2.rawDate);
const itemName2 = `${param2.marker}${title2}`;
const itemValue2 = `${value2}${unit}`;
tooltip2 = `${itemName2}${itemValue2}`;
}
return `${seriesName}${itemName1}${itemValue1}${tooltip2}`;
}
}
};
其中,dataset
的定义如下:
typescriptCopy code- 1
- 2
- 3
- 4
- 5
export interface StatisticsEntity {
rawDate: Date;
date: string;
count: number;
}
export interface StatisticsEntity {
rawDate: Date;
date: string;
count: number;
}
完美。[]~( ̄▽ ̄)~*