上周在实现一个 echarts 图表需求的时候总结了一些小技巧,目前网上没有看到到类似的方案,记录分享一下。
观察下面的图表:
首先这是一个柱状堆积图,每一条柱子有两部分堆积形成。介绍一下数据意义方便理解需求:
一条柱代表一个任务,左半边的长度代表完成任务人数的比例,右半边的长度代表未完成任务人数的比例,加起来必定是 100%,所以每条柱子都一样长占满整行。柱子内的数字为具体人数,最右侧百分比为完成人数的比例。
我们快速实现一个差不多的图表:
const myChart = echarts.init(document.getElementById('main'));
const option = {
dataset: {
source: [['任务名', '完成率', '未完成率', '完成人数', '未完成人数'],
['任务 1', 50, 50, 5, 5],
['任务 2', 4, 96, 2, 50]
]
},
yAxis: {type: 'category'},
xAxis: {},
grid: {containLabel: true},
series: [
{
type: 'bar',
stack: 'samestack',
label: {
show: true,
position: 'insideRight',
formatter: '{@[3]}'
}
},
{
type: 'bar',
stack: 'samestack',
label: {
show: true,
position: 'insideRight',
formatter: '{@[4]}'
}
}
]
};
myChart.setOption(option);
看起来像是这样:
这个图表有两个问题:
- echart 中没有办法简单添加最右侧的百分比 label
- 左半边柱子在数量太小的时候没空间容纳数字
前面说了 echarts 没法设置多个 label,但它支持相当强大的富文本配置。对于第一个问题,我们可以通过富文本标签模拟一个额外的 label。首先,修改右半边柱子的 formatter,让完成率也显示在同一个 label 中。
[
{// 左半边...},
{
type: 'bar',
stack: 'samestack',
label: {
show: true,
position: 'insideRight',
formatter: '{people|{@[4]}} {percentage|{@[1]}%}',
rich: {
people: {color: 'white'},
percentage: {color: 'red'}
}
}
}
]
效果如下:
要把红色的百分比移出柱子外需要 label.distance
和 rich.percentage.width
两个配置:
通过 width 给 percentage 这一个文本块一个固定的宽度,再给 distance 设置赋值配合 position: 'insideRight'
就可以让百分比的文本移出柱子外面。宽度设置为多少并不重要,因为文本是左对齐且没有超出裁剪,所以只要保持一致即可:
label: {
show: true,
position: 'insideRight',
distance: -1,
formatter: '{people|{@[4]}} {percentage|{@[1]}%}',
rich: {
people: {color: 'white'},
percentage: {
color: 'red',
width: 1
}
}
}
效果如下:
到这里第一个问题就解决了,可以继续细调以完全还原设计稿。
我们现在继续看一下另一个问题:如果柱子太窄,柱子内的文本会没有充足空间显示完。
以左半边柱子为例,为了让它在数值较小的情况下也能完全显示,我希望它在 20% 以下的时候显示在柱子外,20% 或以上的时候才显示在柱子内,如下图所示:
同样,这个功能也没有现成的,echarts 也不支持针对单个柱子动态改变 label.position
配置。但我们可以通过预先计算出内部、外部要显示的内容,并在 dataset
中增加额外字段的方式达到这个目的。首先可以先通过 js 为 dataset
扩展两个字段:
const options = {
dataset: {
source: [['任务名', '...', '已完成(内部)', '已完成(外部)'],
['任务 1', '...', 50, '看不见我'],
['任务 2', '...', '看不见我', 2]
]
},
// ...
}
表中的
'看不见我'
仅为演示所用,实际使用中使用空字符串即可。
然后用解决第一个问题相同的方式,在一个 label 中同时显示“已完成(内部)”和“已完成(外部)”两个字段的内容,就可以完成这个需求(没这么简单):
[
{
// ...
label: {
// ...
distance: -0, // 3
formatter: '{inside|{@[5]}}{outside|{@[6]}}', // 1
rich: {
inside: {
color: 'white',
width: 0, // 3
align: 'right' // 2
},
outside: {
color: 'red',
width: 0, // 3
align: 'left' // 2
}
}
},
z: 4 // 4
},
{// 右半边柱子...}
]
我们给左半边柱子的 label 定义了两个富文本格式:inside
和 outside
:
- formatter 中同时显示
已完成(内部)
、已完成(外部)
的内容,但总有其中一个是空字符串,以起到选择性渲染在柱子内部或外部的作用。 -
inside
右对齐,文字变多时向左边生长,outside
相反 - 前面说过 width 具体数值不重要,设置成 0 也是没有问题的
- 由于左边柱子先渲染,会被右边盖住,所以提高 z 值让左边柱子的 label 高于右半边不被遮挡
得到的效果如下:
发生了什么。。。文本对齐的配置没有生效,全部变成居中挤在一起了。略经搜索之后了解到是 ZRender 的一个 bug 导致的。先不去深究,具体在这个例子中的表现是 formatter 中排前面的不能右对齐,排后面的不能左对齐。
那快速 hack 一下在 formatter 中把内外部渲染标签的顺序调换就好了。顺便把 dataset 中的 '看不见我'
改成 ''
以查看最终的效果。
{
// ...
// outside 放前面,inside 换到后面
formatter: '{outside|{@[6]}}{inside|{@[5]}}',
// ...
}
至此我们完美还原了设计稿,并且还优化了一个它未考虑到的边界条件。考虑到篇幅,还有一些旁枝末节的还原工作全都省略掉了,最终效果如下(请脑补最开头那张蓝色图表):