Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to understand how D3 .data key function works

[UPDATE] After another test, it turns out my understanding about key is wrong, the way key matching works is:

When D3 tries to match DOM and data, it will use that key function to extract identifier,

  1. from DOM, it takes out the data value and use that value(could be object) to generate key for DOM element( if the key function use that Data, otherwise just run that key function and give a key value not matter what it is), I used to think it just store another attribute as key inside DOM element and just simply use that value as key, but actually it apply that key function and calculate the key based on current bind data.

  2. from new Data, it directly use each datum to generate the key, and similar like from DOM.

And if the calculated key match each other, then nothing touched on this element, after all matching if can not find the match, then either put DOM into exit() or put data into update(with a plce holder for that going-to-appended DOM).

I have add this similar code here to facilitate others to understand this. The basic diff is this time I use array of object which has consist attribute which can be used as key function to generate key

function addbtn(){
    var keys = [];
    var data = [
        {
            id:0,
            value:"a"
        },
        {
            id:1,
            value:"b"
        },
        {
            id:2,
            value:"c"
        },
        {
            id:3,
            value:"d"
        }
    ]
    var btns = d3.select(".body")
        .selectAll("button")
        .data(data, function(d, i){
           // D3 will take out __data__ from each element, and use its __data__.id as key, 
           // same to the data to be bind, it takes each datum from that data array 
           // and use its id as key to match element's key if matched, go to next, until either there is data left or element left.
           // So when I remove button 2, the comparing will find the element with data id 2 missing( there is data left),
           //so it will put a place holder for an element which will bind the data 2
            return d.id;
        });
    btns.enter()
        .append("button")
        .text(function(d, i){
            return d.value;
        });
    btns.exit().remove();
}

I'm pretty new to the D3 key function as explained here

When I try some code like this:

<button onclick="addbtn()">Add New Button</button>

<div class="body"></div>


    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.14/d3.js"></script>
    <script type="text/javascript">

function addbtn(){
    var keys = [];
    var btns = d3.select(".body")
        .selectAll("button")
        .data([0,1,2,3], function(d, i){
            var k = i;
            keys.push(k);
            return "key_"+k;
        });
    btns.enter()
        .append("button")
        .text(function(d, i){
            return d;
        });
    btns.exit().remove();
}

</script>

My understanding about key is that it's a value which binds to both data and DOM elements to make them to be comparable. If the data and element have same key value (through a certain comparison algorithm), then they are matched and bound. (This is how I think it works but I'm not sure if my understanding is correct or not).

In my experiments, I have specified the key which returns the "key_"+index. To test:

  1. After the first click, there are 4 buttons appended as expected.
  2. I manually remove the button 2, then click again.
  3. I thought the key matching will find that the button with key_2 is missing so it will append a button with key_2 and bind data 2 with it. Surprisingly, it appends button with data 3 instead.

Could anyone help with how to make D3 work like what I expect? I do not want to update anything inside the existing "update collection" that D3 turns into button 0 1 2, but rather I just want to add a missing element, inserting it as button 2.

like image 336
Kuan Avatar asked Jun 04 '26 21:06

Kuan


1 Answers

You're using only the index as the key, and that changes across the different invocations. In particular, the list of indices will not ever have holes. So for your second call, the button with data 3 has index 2 and there's no index 3 as you've removed one button. So it's index 3 that isn't matched.

To make it work, use the actual data in the key function:

.data([0,1,2,3], function(d) { return d; })
like image 88
Lars Kotthoff Avatar answered Jun 06 '26 09:06

Lars Kotthoff



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!