需求场景
我现在需要做一个这样的柱状图,一共3个系列的数据,分别为3个会议类型。从设计图上看,每种类型的会议数量在每个x轴刻度处都是堆叠在一起,可以直观看到每个会议类型在不同月份的数据,也可以看每个月份不同类型的会议的数量比较。
初步思路
通常我会按照正常的写法,除了给echarts的option按照设计图上的样式进行设置,还需要准备3个series项,对应3个类型的会议数量bar。
测试数据和代码如下:
let data={ data:['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'], series:[ {name:"党委会",value:[0,0,1,1,2,0,4,0,0,0,0,0]}, {name:'董事会',value:[0,0,0,1,1,1,4,0,0,0,0,0]}, {name:'总经理办公会',value:[0,1,0,1,0,0,2,0,0,0,0,0]} ] }, colorList=['#6AC4FF', '#4B93C3', '#597789'], axisData=data.data, series=[] data.series.forEach((e, i) => { let total = 0; e.value.forEach(e => { total += e; }); series.push({ name: e.name + `(${total})`, type: 'bar', barWidth: '20', label: { show: false }, itemStyle: { color: colorList[i], borderRadius: [4, 4, 0, 0] }, barGap: '-100%', data: e.value.map((ele, index) => { return { name: axisData[index], value: ele }; }) }); }); let option = { grid: { top: '15%', left: '50', right: '30', bottom: '10%' // containLabel:true }, xAxis: { type: 'category', // boundaryGap: false, axisLabel: { // rotate: 45, color: '#333333', fontSize: '14px', interval: 0 }, axisLine: { show: true, lineStyle: { color: '#EFEFEF' } }, axisTick: { show: false }, splitLine: { show: false }, data: axisData }, yAxis: { type: 'value', // minInterval: 1, nameTextStyle: { color: '333', padding: [0, 34, 0, 0], fontSize: 14 }, min: 0, axisLine: { show: false }, axisLabel: { color: '#333', fontSize: '14px' }, splitLine: { show: true // 是否展示分割线条 } }, legend: { show: true, left: 'center', top: 5, selectedMode: false, itemWidth: 12, itemHeight: 12, data: series.map((e, i) => { return { name: e.name, itemStyle: { color: this.colorList[i] } }; }) }, emphasis: { disabled: true }, tooltip: { formatter: res => { let str = ''; data.series.forEach((e, i) => { str += '<div style="display: flex;align-items: center">' + `<span style=\"display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${this.colorList[i]}\"></span>` + e.name + `<span style=\" padding-left:10px\">${e.value[res.dataIndex]}</span>` + '</div>'; }); return str; } }, series };
效果:
1月tooltip
5月tooltip
7月tooltip
问题
可以看到,option.series这里数组中,如果不给每个series数组项设置z或者zlevel,echarts会默认让后面bar的图层在前面bar之上,也就是会盖住前面series项对应的bar。但我们想要的效果是设计图上那种,数值小的bar盖住数值大的bar,这样有层次地展示,但是我们的数据又不可能永远是第一个series项的每月数值都是最大,然后下一个series项的每月数值都是第二大,第三个series项的每月数值都是第三大,这是不现实的。所以我打算换一种思路
新的思路
我们只修改series,其他代码基本不变,原来我们每个series项对应的是每种不同的会议类型,现在我们改成第一个series项里的data全是每月的最大数值,第二个series项里的data全是每月的第二大数值,以此类推。
不管这个数值是哪个类型的。因为我们对类型的展示只是bar的颜色,而bar的颜色可以通过series项的data数组中的每一数组项来单独设置
我先写一个函数用于返回需要的series
//现在写一个优化重叠的函数
seriesFun(data) {
let series = [],
colorList: ['#6AC4FF', '#4B93C3', '#597789'],
z = data.series.length + 2;
data.series.forEach((e, i) => {
let total = 0;
e.value.forEach(e => {
total += e;
});
//一个一个地push进去,先push的就是在同一投影下较大的集合
//这个total是用于图例处显示总数
series.push({
name: e.name + `(${total})`,
type: 'bar',
barWidth: '20',
z: z++,
label: {
show: false
},
itemStyle: {
borderRadius: [4, 4, 0, 0]
},
barGap: '-100%',
data: e.value.map((ele, index) => {
let item = data.series
.map((sortE, sortI) => {
return {
value: sortE.value[index],
name: sortE.name,
index: sortI
};
})
.sort((a, b) => {
return b.value - a.value;
})[i];
let value = item.value;
return {
name: data.data[index],
value,
itemStyle: {
color: colorList[item.index]
}
};
})
});
});
return series;
}
然后在刚才的option中,series等于这个函数的返回值
而且这里有个需求是,如果数值相等,优先显示党委会,所以在排序函数处加一些条件即可,其他的优先级显示需求也是同理
//现在写一个优化重叠的函数
seriesFun(data) {
let series = [],
colorList: ['#6AC4FF', '#4B93C3', '#597789'],
z = data.series.length + 2;
data.series.forEach((e, i) => {
let total = 0;
e.value.forEach(e => {
total += e;
});
//一个一个地push进去,先push的就是在同一投影下较大的集合
//这个total是用于图例处显示总数
series.push({
name: e.name + `(${total})`,
type: 'bar',
barWidth: '20',
z: z++,
label: {
show: false
},
itemStyle: {
borderRadius: [4, 4, 0, 0]
},
barGap: '-100%',
data: e.value.map((ele, index) => {
let item = data.series
.map((sortE, sortI) => {
return {
value: sortE.value[index],
name: sortE.name,
index: sortI
};
})
.sort((a, b) => {
//这里是个特殊的规则,即如果党委会和其他的类型数量相等,则党委会永远盖住其他的
//所以,如果value相等,则name为党委会的要排后面,其余情况不管,正常排即可
if (
a.value == b.value &&
a.value > 0 &&
(a.name == '党委会' || b.name == '党委会')
) {
return a.name == '党委会' ? 1 : -1;
}
return b.value - a.value;
})[i];
let value = item.value;
return {
name: data.data[index],
value,
itemStyle: {
color: colorList[item.index]
}
};
})
});
});
return series;
}
完整代码
function seriesFun(data) {
let series = [],
colorList: ['#6AC4FF', '#4B93C3', '#597789'],
z = data.series.length + 2;
data.series.forEach((e, i) => {
let total = 0;
e.value.forEach(e => {
total += e;
});
series.push({
name: e.name + `(${total})`,
type: 'bar',
barWidth: '20',
z: z++,
label: {
show: false
},
itemStyle: {
borderRadius: [4, 4, 0, 0]
},
barGap: '-100%',
data: e.value.map((ele, index) => {
let item = data.series
.map((sortE, sortI) => {
return {
value: sortE.value[index],
name: sortE.name,
index: sortI
};
})
.sort((a, b) => { if (
a.value == b.value &&
a.value > 0 &&
(a.name == '党委会' || b.name == '党委会')
) {
return a.name == '党委会' ? 1 : -1;
}
return b.value - a.value;
})[i];
let value = item.value;
return {
name: data.data[index],
value,
itemStyle: {
color: colorList[item.index]
}
};
})
});
});
return series;
}
let data={
data:['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
series:[
{name:"党委会",value:[0,0,1,1,2,0,4,0,0,0,0,0]},
{name:'董事会',value:[0,0,0,1,1,1,4,0,0,0,0,0]},
{name:'总经理办公会',value:[0,1,0,1,0,0,2,0,0,0,0,0]}
]
},
axisData=data.data,
series=seriesFun(data),
colorList: ['#6AC4FF', '#4B93C3', '#597789']
let option = {
grid: {
top: '15%',
left: '50',
right: '30',
bottom: '10%'
// containLabel:true
},
xAxis: {
type: 'category',
// boundaryGap: false,
axisLabel: {
// rotate: 45,
color: '#333333',
fontSize: '14px',
interval: 0
},
axisLine: {
show: true,
lineStyle: {
color: '#EFEFEF'
}
},
axisTick: {
show: false
},
splitLine: {
show: false
},
data: axisData
},
yAxis: {
type: 'value',
// minInterval: 1,
nameTextStyle: {
color: '333',
padding: [0, 34, 0, 0],
fontSize: 14
},
min: 0,
axisLine: {
show: false
},
axisLabel: {
color: '#333',
fontSize: '14px'
},
splitLine: {
show: true // 是否展示分割线条
}
},
legend: {
show: true,
left: 'center',
top: 5,
selectedMode: false,
itemWidth: 12,
itemHeight: 12,
data: series.map((e, i) => {
return {
name: e.name,
itemStyle: {
color: colorList[i]
}
};
})
},
emphasis: {
disabled: true
},
tooltip: {
formatter: res => {
let str = '';
data.series.forEach((e, i) => {
str +=
'<div style="display: flex;align-items: center">' +
`<span style=\"display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${this.colorList[i]}\"></span>` +
e.name +
`<span style=\" padding-left:10px\">${e.value[res.dataIndex]}</span>` +
'</div>';
});
return str;
}
},
series
};
新的效果
可以看到,新的柱状图已经完全实现了需求,而且这个柱状图不仅适用于3个类型数据的场景,任意数量的也满足
总结
在这样的堆叠柱状图的需求场景下,我们把常规的series里写不同会议类型的数据的写法,改成了series里的先放最大的数值一组,再放较小的数值的一组,这样依次push进series。重点是要处理好每个数值原本的类型和它的颜色,本文中已在seriesFun函数中处理了。