利用Echarts内部机制实现散点图框选功能

420 阅读4分钟

Echarts对散点图已经实现了自带的框选的功能,不过是通过toolbox的形式。echarts自带的散点图框选功能,并不能够直接满足我在实际生产中对散点图框选功能的要求。因此我们决定对echarts的源码进行一个更深入的分析,实现一个更灵活的散点图散点框选功能。

散点图的框选.gif

通过分析echarts的源码可知,echarts实现对散点图里的点进行框选,主要依赖于它内部的消息机制。即当用户点击框选功能toolbox的时候,echarts会自动发出一条type为taskGlobalCursor且key为brush的消息,调整echarts的状态。使得echarts能够在用户鼠标按下的时候开始进行框选,而用户鼠标移动的时候同步变更框选范围,最后在用户鼠标松开的时候,绘制出一个完整的框来实现框选功能。

框选有两个比较关键的参数,一个是brushType,这个决定了是什么样的区域选择方式。这个值可以是横向框选、纵向框选、多边形区域选择等多种类型。我们的项目中,设置的是rect,以矩形的方式圈定一定的区域。另一个则是brushMode了,可选的值是代表单框选的single和代表多框选的multiple。在我们的项目中,我们使用的是multiple。

既然echarts内部使用的是消息机制来控制区域的选择,那么我们也是可以使用这种方式来达到相同的目的。事实上,我们也确实是这么做的。下面详细的介绍,我们如何使用echarts内部的消息机制来实现散点图的框选功能。

首先,我们需要先初始化一个echarts的图表,在我们的项目中,我们直接init了一个新的图表:

const chart = echarts.init(dom, undefined, {
	renderer: 'canvas',
	useDirtyRect: false,
});

紧接着,我们需要检查一下这个echarts内部是否有默认的toolbox,如果有默认的toolbox,需要找到这个默认的toolbox里面的brush,并将brushMode改成多选模式。

function setMultipleBrush(chart: echarts.ECharts) {
  const _chart = chart as any;
  const componentsViews: any = _chart._componentsViews;
  for (let i in componentsViews) {
    if (componentsViews[i].type == 'toolbox') {
      const view = componentsViews[i];
      if (view._features && view._features.brush) {
        view._features.brush._brushMode = 'multiple';
      }
    }
  }
}

确保了charts中即使有默认的区域选择,也是多选,而不是单选之后。我们要通过echarts的机制,使用api向echarts实例发送一个新的事件,这个事件将图表的模式切换为区域选择的模式:

function startSelect() {
  const _chart = advanceTFChart as any;
  _chart._api.dispatchAction({
    type: 'takeGlobalCursor',
    key: 'brush',
    brushOption: {
      brushType: 'rect',
      brushMode: 'multiple'
    }
  });
}

通过以上的操作,echarts的图表就可以很好的实现框选功能了。但这还是不够的,因为我们每次框选结束,我们是需要得到相应的数据并进行一系列的操作的。在我们的项目中,还需要根据这些点做出一些转换,生成另外一张新的表。前面我们提到,echarts是通过消息机制来控制区域选择这个功能的。既然是消息机制,那么就必然有发送消息和接受消息的两方。我们可以通过发送消息达到调用echarts的一些功能的目的,同样的,我们也可以通过监听消息,达到捕获echarts的一些事件。而框选结束的事件brushEnd,就是我们要处理的事件。因此我们需要监听brushEnd事件,并在这个事件发生变化的时候,执行一系列的代码。

下面就是我们监听brushEnd事件,并在brushEnd结束后切换框选颜色,绘制一张新表的实现代码。


// 监听brushEnd事件
advanceTFChart.on('brushEnd', function (params: any) {
    let index = params.areas.length;
    const chart = advanceTFChart as any;
    const componentsViews: any = chart._componentsViews;
    
    // 每次框选结束后,都重新调整一下框选的颜色
    // 确保每个框子的颜色都不一样
    for (let i in componentsViews) {
      if (componentsViews[i].type == 'brush') {
        const brushController = componentsViews[i]._brushController;
        if (index >= 10) {
          index = 9;
        }
        brushController._brushOption.brushStyle.fill = BRUSH_STYLES[index].fill;
        brushController._brushOption.brushStyle.stroke = BRUSH_STYLES[index].stroke;
      }
    }
    ...
    for (let i in params.areas) {
      // 处理参数中带回来的数据
      ...
    }
  })

通过使用echarts内部的消息机制,我们就实现了对散点的框选以及框选后事件的处理。同样的,我们如果想要清除所有的框子,也可以通过echarts内部的消息机制来实现。具体的方法是发送两条消息,一条是axisAreaSelect,另一条则是命令为clearbrush消息。具体的实现代码如下:

function clearSelect() {
  const _chart = advanceTFChart as any;
  const api = _chart._api;
  api.dispatchAction({
    type: 'axisAreaSelect',
    intervals: []
  });
  api.dispatchAction({
    type: 'brush',
    command: 'clear',
    // Clear all areas of all brush components.
    areas: []
  });
  brushAreas.value = []
}

不过总得来说,这是一种hacker式的方式。这种方式通过调用echarts内部的api,让我们对图表具备更强的操作能力。但是同样的,将来如果echarts版本升级或内部机制发生了一些变动,我们调整起来也许会有一定的麻烦。