4

上周在实现一个 echarts 图表需求的时候总结了一些小技巧,目前网上没有看到到类似的方案,记录分享一下。

观察下面的图表:

clipboard.png

首先这是一个柱状堆积图,每一条柱子有两部分堆积形成。介绍一下数据意义方便理解需求:

一条柱代表一个任务,左半边的长度代表完成任务人数的比例,右半边的长度代表未完成任务人数的比例,加起来必定是 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);

看起来像是这样:

clipboard.png

这个图表有两个问题:

  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'
        }
      }
    }
  }
]

效果如下:

clipboard.png

要把红色的百分比移出柱子外需要 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
    }
  }
}

效果如下:

clipboard.png

到这里第一个问题就解决了,可以继续细调以完全还原设计稿。

我们现在继续看一下另一个问题:如果柱子太窄,柱子内的文本会没有充足空间显示完。

以左半边柱子为例,为了让它在数值较小的情况下也能完全显示,我希望它在 20% 以下的时候显示在柱子外,20%或以上的时候才显示在柱子内,如下图所示:

clipboard.png

同样,这个功能也没有现成的, 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 高于右半边不被遮挡

得到的效果如下:

clipboard.png

发生了什么。。。文本对齐的配置没有生效,全部变成居中挤在一起了。略经搜索之后了解到是 ZRender 的一个 bug 导致的。先不去深究,具体在这个例子中的表现是 formatter 中排前面的不能右对齐,排后面的不能左对齐。

那快速 hack 一下在 formatter 中把内外部渲染标签的顺序调换就好了。顺便把 dataset 中的 '看不见我' 改成 '' 以查看最终的效果。

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

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

clipboard.png


AlanZhang
2.5k 声望55 粉丝

有趣一点