如何在d3中更新单个数据点而不触及其他元素

How to update a single data point in d3 without touching other elements?

本文关键字:元素 及其他 数据 单个 d3 更新      更新时间:2023-09-26

我想更新单个数据点并仅修改它绑定的元素,但我不知道如何做到这一点。

这篇文章似乎表明var sel = svg.selectAll(...).data(...)给出了更新数据的选择,然后sel.enter(...)代表新数据,然后sel代表更新+新数据。

在这个jsfiddle示例中,我将新元素着色为绿色,将更新的元素着色为蓝色,但似乎每个现有元素都被着色为蓝色,而不仅仅是自上次更新以来更改的元素。如何更新单个数据?

// ...
function update() {
    // clear old classes
    svg.selectAll("text").attr("class","");
    // join to the new data
    var sel = svg.selectAll("text").data(things);
    // update -- but this actually affects all elements in the selection?
    sel.attr("class","update");
    // enter
    sel.enter()
        .append("text")
        .attr("class","enter")
        .attr("x", function(d,i) { return 20*i; })
        .attr("y", 20);
    // update + enter
    sel.text(function(d) { return d; });
    // exit
    sel.exit().remove();
}

正如您所发现的,"update"选项包括所有已存在的元素,准备更新,无论数据是否实际更改。

如果您想测试新数据是否与旧数据相同或不同,您需要一种方法来保存旧数据以与新数据进行比较。然后,您可以使用选择过滤器来丢弃数据相同的元素。

我之前在d3邮件列表中讨论过这个问题。这是我想到的方法:
selection = selection.property(" __oldData__", function(d){ return d; } ); 
  //store the old data as a property of the node
                    .data(newData);  
  //over-write the default data property with new data
selection.enter() /*etc*/;  //handle new elements
selection.filter( function(d) {    
  //test whether the relevant properties of d match the equivalent in the oldData
  //also test whether the old data exists, to catch the entering elements!
                 return ( (this.__oldData__ ) && 
                          (d.value != this.__oldData__.value) );
            })
            .style("fill", "blue");
selection.property("__oldData__", null);     
  //delete the old data once it's no longer needed

您当然可以为旧的data属性使用任何名称,只是惯例是在它周围添加许多"_"字符,以避免混淆浏览器的任何本机DOM属性。你不需要在之后删除oldData(它将在下次更新时被覆盖),但是如果你不经常更新,它可以节省内存来显式释放它。

注意selection.filter()不保留索引值。如果您需要跟踪i,您可以添加一个额外的步骤来保存索引,然后将其作为一个单独的属性(元素或数据对象的属性)进行过滤,或者您可以跳过过滤器,直接在style/attr调用中的函数中进行测试。

编辑:我已经改变了过滤器功能,相对于链接的讨论,所以它只有包含更新的元素,而不是新的更新的元素。无论哪种方式,过滤器都会选择过滤器函数返回true的元素。

在您的情况下,弄清楚这一点相对容易。您将元素的文本设置为数据,因此在确定数据是否已更新时,只需比较它们:

sel.classed("update", function(d) { return d != d3.select(this).text(); });

完整演示在这里。关于一般解决方案,请参见amelabr的答案。