Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 - forEach is not a function when upgrading from v3 to v4

I am trying to upgrade this stackable bar chart to v4. Everything works except for one thing.

When I filter one category the bars don't drop to the start of the x-axis. I get an error which says:

state.selectAll(...).forEach is not a function

I've tried multiple things but I can't figure this one out. This is the broken code:

function plotSingle(d) {

class_keep = d.id.split("id").pop();
idx = legendClassArray.indexOf(class_keep);

//erase all but selected bars by setting opacity to 0
d3.selectAll(".bars:not(.class" + class_keep + ")")
    .transition()
    .duration(1000)
    .attr("width", 0) // use because svg has no zindex to hide bars so can't select visible bar underneath
    .style("opacity", 0);

//lower the bars to start on x-axis
state.selectAll("rect").forEach(function(d, i) {

    //get height and y posn of base bar and selected bar
    h_keep = d3.select(d[idx]).attr("height");
    y_keep = d3.select(d[idx]).attr("y");

    h_base = d3.select(d[0]).attr("height");
    y_base = d3.select(d[0]).attr("y");

    h_shift = h_keep - h_base;
    y_new = y_base - h_shift;

    //reposition selected bars
    d3.select(d[idx])
        .transition()
        .ease("bounce")
        .duration(1000)
        .delay(750)
        .attr("y", y_new);

})
}

I find it strange that this works flawlessly in D3 v3, why wouldn't this work in v4?

like image 949
kevin taffee Avatar asked Nov 02 '17 15:11

kevin taffee


1 Answers

In d3 v3 selectAll returned an array, in d3 v4 it returns an object.

From the v3 notes:

Selections are arrays of elements—literally (maybe not literally...). D3 binds additional methods to the array so that you can apply operators to the selected elements, such as setting an attribute on all the selected elements.

Where as changes in v4 include:

Selections no longer subclass Array using prototype chain injection; they are now plain objects, improving performance. The internal fields (selection._groups, selection._parents) are private; please use the documented public API to manipulate selections. The new selection.nodes method generates an array of all nodes in a selection.

If you want to access each node in v4 try:

selection.nodes().forEach( function(d,i) { ... })

But, this is just the node, to get the data you would need to select each node:

var data = [0,1,2];

var svg = d3.select("body").append("svg")
  .attr("width",500)
  .attr("height",200)

var circles = svg.selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", function(d,i) { return i * 20 + 50 })
  .attr("cy", 50)
  .attr("r", 4);
  
  
circles.nodes().forEach(function(d,i) {
    console.log(d3.select(d).data());
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

But, if you need the data or to modify the selection properties, it could be easier to use selection.each(). d3.each iterates through each element of a d3 selection itself, and allows you to invoke a function for each element in a selection (see API docs here):

var data = [0,1,2];

var svg = d3.select("body").append("svg")
  .attr("width",500)
  .attr("height",200)

var circles = svg.selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", function(d,i) { return i * 20 + 50 })
  .attr("cy", 50)
  .attr("r", 4);
  

  
circles.each( function() {
  console.log(d3.select(this).data());  
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

In v3 of this bar chart, in the forEach loop

`states.selectAll("rect").forEach(function(d,i) {`

d is an array of nodes (the rectangles in each .g).

But, in v4 d3 selections aren't arrays, you can't use a forEach loop in the same way. But you can still get the nodes in it without much modification using selection.nodes() and than get the childNodes to replicate the array in the v3 version:

state.nodes().forEach(function(d, i) {
            var nodes = d.childNodes;

Here we go through each element/node in state and get the child rects, returned as an array. Here's an updated fiddle.

like image 180
Andrew Reid Avatar answered Nov 15 '22 05:11

Andrew Reid