Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

selectAll works only once in D3

I am going mad on this one...
On first call, refreshGraph() works perfectly : my graph is displayed.
On following calls, nothing happens. selectAll() returns an array of arrays as expected, but my graph does not change.
BUT... If I use select() instead of selectAll(), it works !
AND WORST... If I add join.select(".bar") before the join.selectAll(".bar") it works also !

Anyone could explain me why ?

I do not understand this behavior. I perfectly understand what select() and selectAll() are expected to return. Calling style() on both returned subselections should give the same results since data are the same, even if organized by group with selectAll, and flat with select. Am I right ?

var srcData = [4, 8, 15, 16, 23, 42];

x = d3.scale.linear().domain([0, 42]).range([0, 420]);

function refreshGraph() {

    join = d3.select(".chart").selectAll(".container").data(srcData);

    join.enter()
    .append("span").attr("class","container")
    .append("div").attr("class","bar");

    join
    .selectAll(".bar") // wont work, but will if select() instead of selectAll()
    .style("width", function(d) { 
        return x(d) + "px";
    });
}

function doChange() {
    for (var i = 0; i < srcData.length; i++) {
        srcData[i] /= 2;
    }
    refreshGraph();
}

refreshGraph();
like image 398
user3173982 Avatar asked Dec 20 '22 16:12

user3173982


1 Answers

After hours and hours of reading the doc, and stepping into the D3 code, I finally get it. I combined several cases, which led me to stack of confusions. So, what is this about ?

 join = d3.select(".chart").selectAll(".container").data(srcData);
 join.enter()
.append("span").attr("class","container")
.append("div").attr("class","bar");

The append() function does something tricky to help you :

Appends a new element with the specified name as the last child of each element in the current selection, returning a new selection containing the appended elements. Each new element inherits the data of the current elements, if any, in the same manner as select for subselections.

BUT... The inherit word is misleading. You have to read : Each new element receives a copy of the data from current elements

As the way D3 works, there is no magic binding. The same way you re-bind data each time you want to reflect the change of values, you have to set the data in the correct nodes. The append() function did the job for you, the select() function can do it as well, but the selectAll() DOES NOT.

For each element in the current selection, selects descendant elements that match the specified selector string. ... The subselection does not inherit data from the current selection;

In my question code, at the first call, the data is copied from .container to .bar elements by append(). As the data do not change in quantity (no new/ no less values), the subsequent calls bypass the append() function (enter() section is empty) and selectAll() will get the correct nodes (.bar classed) which still have the previous data version. When style() is called, this not the new data that are applied.

And of course, using select() make it works since it copy the data from the ancestor to the subselection element.

nrabinowitz gave a correct anwser : if you absolutly want to use selectAll, you have to use a function to retrieve the data for grouped nodes.

selectAll() doc says : The returned selection is grouped by the ancestor node in the current selection ... if the data value is specified as a function, this function will be called with the data d of the ancestor node and the group index i to determine the data bindings for the subselection.

Hope it will help...

like image 148
user3173982 Avatar answered Dec 24 '22 01:12

user3173982