深入 echarts 如何设置 Label / axisLabel 换行及自定义格式 / 自定义样式? 结合 canvas 如何实现文字换行来理解

前言

之前在一篇文章《深入 echarts 如何设置渐变色?echarts 如何设置圆角?echarts 如何设置自定义 Label?》已经初步介绍了如何通过 formatter 结合 rich 来处理 label 自定义文字样式,但我们会经常遇到一些情况,就是 echarts 的 x 轴文字太长显示不全或者 label 文字过长被隐藏的情况。如何要实现 label / Label / axisLabel 里面的内容设置换行及同时单独设计样式呢?之前并没有涉及到。在这里,我们结合最基础的 canvas 实现换行的例子来深入了解,echarts 实现换行及同时单独设计样式的原理。

在 canvas 里面是如何设置换行呢?

我们先看看我在项目中经常用到的已经封装好的在 canvas 如何实现文字换行的方法

/**
 * canvas 换行画出多行文字
 * @param  {CanvasRenderingContext2D} ctx
 * @param  {String} text
 * @param  {Number} x
 * @param  {Number} y
 * @param  {Number} w,即一行的宽度 width
 * @param  {String} color,即字体的颜色
 * @param  {Number} fontSize
 * @param  {Boolean} isBold,即是否加粗
 */
export function drawMultiLineRichText (ctx, text, x, y, w, color, fontSize, isBold) {
  if (!text) {
    return false
  }
  const rawRows = text.split('\n')
  let rows = []
  // 保存画布 用 save 及 restore 是为了不影响其他地方使用画布
  ctx.save()
  fontSize = fontSize ? fontSize : 26
  ctx.font = `${isBold ? '700' : '400'} ${fontSize}px "Hiragino Sans GB W3","Microsoft YaHei",sans-serif`
  ctx.textBaseline = 'middle'
  ctx.fillStyle = color
  rawRows.forEach((txt) => {
    const chars = txt.split('')
    let tempStr = ''
    chars.forEach((char) => {
      if (ctx.measureText(tempStr).width >= w) {
        rows.push(tempStr)
        tempStr = ''
      }
      tempStr += char
    })
    rows.push(tempStr)
  })
  rows.forEach((txt, n) => {
    ctx.fillText(txt, x, y + n * fontSize * 1.8) // ****** 使用 FILLTEXT
  })
  ctx.restore() // 恢复画布
}

由上面的代码,我们可以知道在 canvas 上面实现文字换行,思路是使用 CanvasRenderingContext2DmeasureText 方法来测量文字的长度然后把字符串切割成多行单独在画布上面使用 fillText 定位画出来。而 echarts 是如何实现的呢?下面来揭晓吧。

在 echarts 里面对 label / Label / axisLabel 里面的内容设置换行及同时单独设计样式

实现思路也类似,把字符串按多少个字一行这样把字符串切割再用 \n 拼接到一起,自定义格式则使用 rich 来处理,这全部操作在上是在 formatter(val) 函数上面通过 return 一个最终值来完成。下面来看最终代码:

一、不区分英文字符 / 中文字符处理

    // 先定义好用于自定义样式的 rich
    const rich = {
      b: {
        fontSize: 18,
        fontWeight: 'bold'
      },
      n: {
        fontSize: 14,
        lineHeight: 20
      }
    }
	// 【这是实现换行的关键函数】定义一个换用于换行的函数,意思就是每多少个字符 charQty 就换行
    const makeMultiLine = (str, charQty) => {
      const strs = str.split('')
      const len = strs.length
      if (len < charQty + 1) {
        return str
      }
      let result = ''
      strs.forEach((_, index) => {
        result += _
        if ((index + 1) % charQty === 0 && index < len - 1) {
          result += '\n'
        }
      })
      return result
    }
	// ... 其他代码
	// ... 其他代码
        xAxis: {
          show: true,
          axisLabel: {
            formatter: function (val) {
              // console.log(val)
              let str = makeMultiLine(val, 5) // 每 5 个字符一行
              return `{n|${str}}` // 使用 rich 中的 n 来设置样式
            },
            rich,
			interval: 0 // 显示所有的 x 轴上的文字不隐藏
          }
        },
	// ... 其他代码
        label: {
          show: true,
          formatter: function (val) {
            let str = makeMultiLine(val.name, 5) // 每 5 个字符一行
            return `{n|${str}}\n\n{b|${val.percent}%}` // 使用 rich 中的 n 和 b 来对两段内容单独设置样式
          },
          rich
        }
    // ... 其他代码

二、区分英文字符 / 中文字符处理

	    xAxis: {
		  data: myCategories, // 假设 X 轴数据是数组 myCategories 
	      axisLabel: {
			formatter: function (val) {
				var strs = val.split('');
				var count = myCategories.length || 1; // 后面要把宽度等分
				var len = val.replace(/[\u4e00-\u9fa5]/g, 'aa').length; // 计算总英语字符长度
				var result = '';
				var maxLen = myWidth / count / 7; // 假设外围宽度是 myWidth,字符 font-size 是 12px,则英语单词宽度大约为 7
				if(len > maxLen) {
					var strLen = 0;
					for(var i = 0; i < strs.length; i++) {
						if(strs[i].charCodeAt(0) > 255) { // 如果是中文字符,则长度 +2
							strLen += 2
						} else {
							strLen += 1
						}
						if(strLen < maxLen) {
							result += strs[i];
						} else {
							break;
						}
					}
					result += '...';
					return result;
				}
				return val;
			}
	      }
	    }

echarts 饼图调整上下左右边距解决右边 Label 显示不全的问题

还有一种情况,就是饼图文字往往会挤到右边,但是饼图不能通过 grid 来调整边距的,因此,我们可以考虑把饼图左移一下,这样就不会出现右边 label 太长被隐藏的问题。代码如下:

// ... 其他代码
        series: [{
          type: 'pie',
          radius: '55%',
          center: ['47%', '50%'], // 通过这里来设置 饼图 上下左右边距
          label: {
            formatter: '{d}%\n{c}\n{b}'
          }
        }]
// ... 其他代码