给echarts图表添加空白区域点击事件

2,659 阅读3分钟

客户的需求

客户是上帝,现在客户说这个图表上的点击事件不好操作,特别是在折线上点击时,我无法点中!这时项目经理说,像这样在空白处有图例显示时就能点中可不可以?客户说,中,我要的就是这个效果!这时程序员如果在现场就感觉,哇,真棒,这个需求棒棒哒!

现实情况

你翻遍了echarts的官方文档,在echarts官方文档的事件栏目中有这样一段话:在 ECharts 中事件分为两种类型,一种是用户鼠标操作点击,或者 hover 图表的图形时触发的事件,还有一种是用户在使用可以交互的组件后触发的行为事件,例如在切换图例开关时触发的 legendselectchanged 事件(这里需要注意切换图例开关是不会触发 legendselected 事件的),数据区域缩放时触发的 datazoom 事件等等。也就是说现实情况其实就是巧妇无米,在事件那块的api中,所有的事件里都没有点击图表空白地方触发的,比如click事件,这个只能在点击到图表要素上时才能触发;

困局破解

到目前这地步,只看官方文档已经不解渴了,如果你还没放弃,试着问下AI,它可能会给你提供一些思路,它能帮你省去看源代码的功夫。在 ECharts 中,点击空白区域(即未绑定数据的区域)默认并不会触发 click 事件,但是,ECharts 使用 ZRender 作为其渲染引擎,提供了对整个画布进行事件监听的能力。你可以通过监听底层的渲染引擎(ZRender)的 click 事件来捕获整个画布上的点击事件,包括空白区域。

这就是说,我可以通过 myChart.getZr() 返回 ZRender 实例,然后监听它上面的 click 事件,通过这种方式,就可以捕获到整个图表区域内的所有点击事件,无论你是否在数据点或图形上。

我将信将疑地试了下:

this.$refs.flowChat.chart.getZr().on('click', (e) => {
        debugger
      })

调试后发现果然进入了断点,心里惊喜万分,程序员的快乐暴击了。

抓住线索

前面已经找到了这个事件,这还不够,我得通过这个事件回调找到对我有用的参数,通过断点监听发现回调包含了这些参数:

有用的参数就是offsetX和offsetY,这两个是当前鼠标位置相对整个echart边框的水平和垂直方向上的偏移量。根据这两个参数,我就确定了鼠标的位置,然后在鼠标所有位置的垂直线上,肯定相交了一条数据,这条数据的x轴就可以拿到了,而我可以写一个算法,通过这个x轴的关系找到折线上的真实数据;

还需要一个api

这里还有一个关键的api:convertFromPixel,在对应官方文档上有说明:转换像素坐标值到逻辑坐标系上的点。也就是说,虽然当前点击位置没有折线经过,但我也可以通过前面点击获取的偏移值offsetX和offsetY计算出当前点在坐标轴上的真实x、y值,这个x值就是我要的,通过这个x值我可以遍历y轴对应的series中的值,找到y值。还有一个api要用到,通过echart对象的getOption方法可以获取到当前实例的所有option对象,这里有你想要的series及其data。

// todo
this.$refs.flowChat.chart.getZr().on('click', (e) => {
        debugger
        getRealData(e.offsetX, e.offsetY)
})
getRealData() {
  const option = myChart.getOption()
  const series = option.series
  // todo
  // 通过e.offsetX和e.offsetY找到series中与点击点相交的那条数据。
}

还需要一个算法

基本上到这一步,聪明的程序就不需要往下看了,自己动手啪啪啪写完剩下的了。我这里还是帮人帮到底,把我的算法贴出来,即是记录也可以帮到需要的人。

function getRealData(myChart, x, y) {
        const option = myChart.getOption()
        const series = option.series
        let nearestData = null
        let minDistance = Infinity

        series.forEach((s) => {
          if (s.type === 'line') {
            const data = s.data
            data.forEach((item) => {
              const coord = myChart.convertFromPixel({ seriesIndex: 0 }, [x, y])
              const distance = Math.abs(coord[0] - item[0])
              if (distance < minDistance) {
                minDistance = distance
                nearestData = {
                  seriesName: s.name,
                  data: item,
                  dataIndex: data.indexOf(item)
                }
              }
            })
          }
        })

        return nearestData
      }

通过getOption获取到data对象之后,遍历,然后通过convertFromPixel逆运算得到我想要的一个坐标值(x,y),然后再遍历data对象,将点击点的x值与data中存的x值相减并求绝对值,通过冒泡算法寻找,差值最小的就是我要的数据。

还有小技能包哟

这里要注意一点可能你会有疑问,就是data是一个数组,一般只会存有用的x或者y要素,但其实你在初始化series时,可以在添加了x、y要素之后再push一个完整的包含x、y的对象,这样我的这个整个流程就成了一个闭环了,因为data是一个数组,通过上面的算法找到了点击点与折线相交的x坐标值就找到了索引然后就能找到对应的完整的x、y了。

ok,到这里就结束了,尽情的在echart上点击吧,哪怕你瞄不准折线上的点也无所谓,尽管肓点,一定能选中!

参考资料: