#在51CTO的第一篇博文#

这是一篇新人文章

自我介绍

hello 大家好!很高兴来到51CTO博客。目前从事前端开发五年有余,以前主要在酒店文旅行业工作,主要做一些酒店管理系统和酒店预订小程序、APP等;受疫情影响公司裁员,目前主要深耕web可视化、大屏数据展示,使用到的技术主要Vue3, canvas,SVG多一点,框架方面Ecahrts,G2, G6, D3等都使用,个人喜欢用antV系列多一点。但图表性能方面,感觉还是echarts要强一点。以上纯属个人喜好,如有异议,以你的为准。 希望在这里能学到更多的知识,并且能够写出优秀的博客。

技术分享

1.echarts 折柱线混合label互相遮挡

image.png

这种折柱混合图形,当label经常容易重叠。

Echarts 解决方案>>>

series-bar.labelLayout.moveOverlap
在标签重叠的时候是否挪动标签位置以防止重叠。

目前支持配置为:

'shiftX' 水平方向依次位移,在水平方向对齐时使用
'shiftY' 垂直方向依次位移,在垂直方向对齐时使用

image.png 个人亲测尝试了一下,目前这个API主要主要针对同一个series系列有一定的优化作用,不同系列间的label无法解决。

G2 解决方案>>>

overlapDodgeY 位置碰撞的标签在 y 方向上进行调整,防止标签重叠。
chart
  .line()
  .encode('x', 'letter')
  .encode('y', 'frequency')
  .encode('color', 'type')
  /* ... */
  .label({
    text: 'frequency',
    transform: [
      {
        type: 'overlapDodgeY',
      },
    ],
  });

个人也写了一个demo测试了一下,没有深入的了解。G2可以解决同类型的label重叠问题, 比如说多条折线图之间label重叠,但是它也还是不能优化折柱混合label之间的重合问题。当然G2还有一些其它针对优化可做选择,比如:

overflowHide 对于标签在图形上放置不下的时候,隐藏标签。
overlapHide 对位置碰撞的标签进行隐藏。算法逻辑是碰撞的两个标签,保留前一个,隐藏后一个。

还可以自定义碰撞优化方法。

me

受echarts和G2处理这种label碰撞优化方案启发,只考虑折柱混合这种情况。

series-bar.labelLayout 属性
标签的统一布局配置。
该配置项是在每个系列默认的标签布局基础上,统一调整标签的(x, y)位置,标签对齐等属性以实现想要的标签布局效果。

该配置项也可以是一个有如下参数的回调函数

/ 标签对应数据的 dataIndex
dataIndex: number
// 标签对应的数据类型,只在关系图中会有 node 和 edge 数据类型的区分
dataType?: string
// 标签对应的系列的 index
seriesIndex: number
// 标签显示的文本
text: string
// 默认的标签的包围盒,由系列默认的标签布局决定
labelRect: {x: number, y: number, width: number, height: number}
// 默认的标签水平对齐
align: 'left' | 'center' | 'right'
// 默认的标签垂直对齐
verticalAlign: 'top' | 'middle' | 'bottom'
// 标签所对应的数据图形的包围盒,可用于定位标签位置
rect: {x: number, y: number, width: number, height: number}
// 默认引导线的位置,目前只有饼图(pie)和漏斗图(funnel)有默认标签位置
// 如果没有该值则为 null
labelLinePoints?: number[][]

Echarts提供的labelLayout回调,可以取到当前label的位置(x,y)和宽高(w,h),并返回新的坐标(x,y)。 当series=[bar, line],

第一步

echarts根据series顺序先渲染bar-x1、bar-x2、bar-x3..., 此时将[bar-x1,bar-x2,bar-x3...]label的坐标位置存储起来。

第二步

echarts 渲染完bar组件,开始渲染line-x1, line-x2, line-x3..., 此时判断 line-x1的label是否和bar-x1的label是否碰撞,

第三步

若bar-x1与line-x1碰撞,则修改line-x1坐标位置并返回

demo代码

/**
 * 处理多series系列, 不同系列之间label重叠
 * @param barSeries
 */
export function lineLabelLayout(barSeries) {
  const _labelList = { 0: {} }

  barSeries.forEach(item => {
    item.labelLayout = param => {
      const xy = layout(param)
      return {
        x: xy?.x || param.labelRect.x,
        y: xy?.y || param.labelRect.y
      }
    }
  })

  function layout(params) {
    // labelRect: {x: number, y: number, width: number, height: number} 默认的标签的包围盒,由系列默认的标签布局决定 从左上角开始算起
    const { dataIndex, seriesIndex, labelRect, rect } = params
    if (dataIndex === undefined) {
      return
    }
    const labelSeriesIndex = _labelList[seriesIndex] || {}
    if (seriesIndex === 0) {
      // 第一系列,直接添加并返回
      labelSeriesIndex[dataIndex] = { ...params }
      _labelList[0][dataIndex] = { ...params }
      return { x: labelRect.x, y: labelRect.y }
    } else if (seriesIndex === 1) {
      // 非第一系列,
      const preSeries = _labelList[0][dataIndex]
      const preY = preSeries.labelRect.y
      const y = labelRect.y
      const diff = y - preY
      const maxDiff = Math.max(preSeries.labelRect.height, labelRect.height)
      let newY = labelRect.y
      // 判断 两个系列的label Y轴上是否存重叠
      // todo 只处理了,两个系列 Y轴上的重叠; 多系列参考 left/right, top/bottom设置修改
      if (Math.abs(diff) <= maxDiff) {
        if (diff < 0) {
          newY = y - (maxDiff + diff) - 2
        } else {
          newY = y + (maxDiff - diff) + 2
        }
      }
      return { x: labelRect.x, y: newY }
    }
  }

  return barSeries
}

flag!

明天更好!