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]}}',      // ...  }

至此我们完美还原了设计稿,并且还优化了一个它未考虑到的边界条件。考虑到篇幅,还有一些旁枝末节的还原工作全都省略掉了,最终效果如下(请脑补最开头那张蓝色图表):