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);

看起來像是這樣:

這個圖表有兩個問題:

  1. echart 中沒有辦法簡單添加最右側的百分比 label
  2. 左半邊柱子在數量太小的時候沒空間容納數字

前面說了 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.distancerich.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 定義了兩個富文本格式:insideoutside

  1. formatter 中同時顯示已完成(內部)已完成(外部)的內容,但總有其中一個是空字元串,以起到選擇性渲染在柱子內部或外部的作用。
  2. inside 右對齊,文字變多時向左邊生長,outside 相反
  3. 前面說過 width 具體數值不重要,設置成 0 也是沒有問題的
  4. 由於左邊柱子先渲染,會被右邊蓋住,所以提高 z 值讓左邊柱子的 label 高於右半邊不被遮擋

得到的效果如下:

發生了什麼。。。文本對齊的配置沒有生效,全部變成居中擠在一起了。略經搜索之後了解到是 ZRender 的一個 bug 導致的。先不去深究,具體在這個例子中的表現是 formatter 中排前面的不能右對齊,排後面的不能左對齊。

那快速 hack 一下在 formatter 中把內外部渲染標籤的順序調換就好了。順便把 dataset 中的 '看不見我' 改成 '' 以查看最終的效果。

{      // ...      // outside 放前面,inside 換到後面      formatter: '{outside|{@[6]}}{inside|{@[5]}}',      // ...  }

至此我們完美還原了設計稿,並且還優化了一個它未考慮到的邊界條件。考慮到篇幅,還有一些旁枝末節的還原工作全都省略掉了,最終效果如下(請腦補最開頭那張藍色圖表):