d3可以根据输入的数据来绘制各种图形,如果数据更新了呢?需要刷新图形,这个时候要注意,刷新图形和绘制图形是不同的。

绘制图形需要先绑定数据,然后根据数据的size确定要生成多少个svg节点,然后再根据节点去处理各个数据

d3.selectAll('path')
  .data(points)
  .enter()
  .append('path')
  .attr('d', (d) => generator(d))
  .attr('linenum', (d, i) => i)
  .attr('stroke', color)
  .attr('stroke-width', '2px')
  .attr('fill', 'none')
  .exit().remove()

数据更新分成两种,一种是数据本身的size不变,变化的是其中的数据本身。

但是也要注意,数据的那个维度的size对应着svg节点。比如一个三维的array,其中第0维度对应的是线条,第1维度对应的是线条上的点,第2维度对应的是点的坐标xy值。对应于d3的数据格式来讲,其实只有第0维度是size,其他两个维度都是数据。如果第1维度的数据变化了,比如增加了或者减少了,也就是说某个线条上的点新增了或者删除了。

对于上面的代码来讲,第0维度对应的是一个个path节点,第1维度对应的是generator,这个generator不仅包含linetype,还要包含scale以及d中哪个对应的是x坐标,哪个对应的是y坐标。

其中path节点的数量对应的就是数据本身的size,而第1维度和第2维度的数据对应的就是其内部的数据。

如果数据的size变化了,则分为3种:增加/删除。

增加分为2种,前一篇文章已经介绍了最复杂的插入。

如果是追加,那么就和绘制采用同样的处理方法即可,d3会比较绑定的数据的size和实际的svg节点(上面代码中是path)的数量,然后根据数据的size追加svg节点,然后让追加的svg节点对应的数据通过generator处理后,生成svg图像。前面已经绘制过的节点不会被重新绘制,节省计算量。

删除也分为2种,在最后删除和在其他位置删除。如果是在最后删除,也很简单,直接

d3.selectAll('path')
  .data(points)
  .exit().remove()

如果是其他位置删除,需要写一段代码找到对应的parentNode和targetNode,如果需要d3风格,那么就

d3.select(parentNode).select(targetNode).remove()

当然也可以用原生的JavaScript直接操作DOM

parentNode.removeChild(targetNode)

如果除了对节点进行删除操作,还需要做一些其他操作,比如在节点中保存了其序列号,那么如果删除了中间一个节点,其后续的节点的序列号需要顺序提前,这需要使用其他的代码来处理。处理方法就是DOM操作,d3的选择集有比较方便的语法糖来处理这种操作,也可以用原生来JavaScript处理。

如果数据的size不变,变化的是数据的内容,那么处理方法和绘制类似,不过要注意不能用

d3.selectAll('path)
  .enter()
  .append('path)
  .exit()
  .remove()

就是说调整数据和对应节点对应关系的enter/append/exit/remove都不能用,否则d3就会简单查看其对应关系是否存在,而不会对其中的内容做更新。

正确方法是绑定数据直接来绘制

d3.selectAll('path')
  .data(points)
  .attr('d', (d) => generator(d))
  .attr('linenum', (d, i) => i)
  .attr('stroke', color)
  .attr('stroke-width', '2px')
  .attr('fill', 'none')

也就是说,只要你确认svg节点数量和data的size匹配之后,不需要利用enter/append/exit/remove来调整对应关系,而是指定数据后直接绘制即可。d3就不会查看数据和节点的对应关系,而是会直接重绘。

Logo

前往低代码交流专区

更多推荐