Echarts 添加多個 label 與動態調整 position 的方案
- 2019 年 10 月 4 日
- 筆記
首先這是一個柱狀堆積圖,每一條柱子有兩部分堆積形成。介紹一下數據意義方便理解需求:
一條柱代表一個任務,左半邊的長度代表完成任務人數的比例,右半邊的長度代表未完成任務人數的比例,加起來必定是 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]}}', // ... }
至此我們完美還原了設計稿,並且還優化了一個它未考慮到的邊界條件。考慮到篇幅,還有一些旁枝末節的還原工作全都省略掉了,最終效果如下(請腦補最開頭那張藍色圖表):