Using selection.each and updating attributes

5,023 views
Skip to first unread message

Namit Setia

unread,
Jan 9, 2013, 5:04:00 PM1/9/13
to d3...@googlegroups.com

My question is about this method for adding elements to other elements using selection.each: 

http://stackoverflow.com/questions/13203897/d3-nested-appends-and-data-flow/13204545#13204545

var data = [{ "label": "chocolate", "text": "Chocolate Cookie", "img": "chocolate.jpg" },
        { "label": "sugar", "text": "Sugar Cookie", "img": "sugar.jpg" }];

d3.select("body").selectAll("div")
    .data(data)
  .enter().append("div")
    .attr("id", function(d) { return d.label; })
    .each(function(d) {
        d3.select(this).append("p")
          .text(d.text);
        d3.select(this).append("img")
          .attr("src", d.img);
    });

This example is easy enough to understand, but how can I update values?  Based on the enter/upate/remove, it seems like it should be the following, but I'm not sure how to select the individual elements using d3.select(this).

var g = d3.select("body").selectAll("div")
    .data(data);

  g.enter().append("div")
    .attr("id", function(d) { return d.label; })
    .each(function(d) {
        d3.select(this).append("text")
          .text(d.text1);
        d3.select(this).append("text")
          .text(d.text2);
    });
  g.attr("id", function(d) { return d.label; })
    .each(function(d) {
//update text?
    });

Ian Johnson

unread,
Jan 9, 2013, 6:12:43 PM1/9/13
to d3...@googlegroups.com
rather than use each, it's more convenient to "save your selection". also you will want to make each text element selectable in a reliable maner, using something like a class

var divs = d3.select("body").selectAll("div")
    .data(data)
  
.enter().append("div")
    .attr("id", function(d) { return d.label; })
divs.append("text").text(function(d) { return d.text1 }).classed("t1", true);
divs.append("text").text(function(d) { return d.text2 }).classed("t2", true);

then later on you can do do
var update = d3.select("body").selectAll("div")
    .data(newdata)
update.select("text.t1").text(function(d) { return d.text1 })
update.select("text.t2").text(function(d) { return d.text2 })

On Wed, Jan 9, 2013 at 2:04 PM, Namit Setia <nami...@gmail.com> wrote:
d3.select("body").selectAll("div") .data(data) .enter().append("div") .attr("id", function(d) { return d.label; })



--
Ian Johnson - 周彦

Alex Brown

unread,
Jan 10, 2013, 9:17:37 AM1/10/13
to d3...@googlegroups.com
Convenience is in the eye of the programmer, Ian!

A functional programmer might not agree.

If each works for update then its at least an option,

-Alex

Alex Brown

unread,
Jan 10, 2013, 9:27:26 AM1/10/13
to d3...@googlegroups.com
I was going to explain how you should

Each(
D3.select(this).select("text.firstclass").text(...d.text1)
D3.select(this).select("text.firstclass").text(...d.text1)
)

But then I noticed that you hadn't identified your text-elements by class.

It's not necessary to do that since you should be able to address by position, but:

Since both of your sub elements are text you might also consider removing the each from the enter and handling it in the update, using select all:

textU=update.selectAll("text")
.data(function(d){return
[d.text1,d.text2]}).enter()

textU.enter().append("text")
textU.text(function(d){return(d)})

-Alex

Namit Setia

unread,
Jan 10, 2013, 10:47:41 AM1/10/13
to d3...@googlegroups.com
I'm still kind of confused as to how to get it to update/remove properly.

My <g>'s are dynamically be created/updated/removed and the elements within those <g>'s is dynamically being crated/updated/removed.

Ian -- I'm not sure how newData is related to Data.  Ideally I would like to use the same datastructure for both the <g>'s and the elements inside (i guess i'm thinking of it like a class:

<g>
  <element1/>
  <element2/>
</g>
<g>
  <element1/>
  <element2/>
</g>
etc 

Alex -- I tried doing: 

var ge = vis.selectAll("g").data(dd,function(d){ return d.data.name+d.x+d.y+d.s; });
var gs = ge.enter().append("g")
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });

               var update = vis.selectAll("g").data(function(d) { console.log(d); return d; });

but the data doesn't seem to be there in the update.

I think I like the .each approach, though.  How do you access the sub elements by position?

Ian Johnson

unread,
Jan 10, 2013, 12:39:44 PM1/10/13
to d3...@googlegroups.com

By convenience I meant less typing, you are basically reselecting stuff you already have selected by doing it that way. Not sure what it has to do with functional programming.

As for update/exit your text element subselections can behave the same the same way as your group elements. Meaning if you use alex's suggestion of var texts = g.selectAll("text").data([d.text1,d.text2])

You can do enter/update/exit pattern on the selection saved in the texts variable.

Tore Nauta

unread,
Jan 10, 2013, 4:56:11 PM1/10/13
to d3...@googlegroups.com
I guess various methods have there pros and cons. Two variations for Namit's initial example, one with and one without the use of each:

// DATA JOIN
var divs = d3.select("body").selectAll("div")
    .data(data);

// ENTER
// create the elements and set attributes not related to bound data
divs.enter().append("div")
    .each(function() {
        var s = d3.select(this);
        s.append("p");
        s.append("img");
    });

// ENTER + UPDATE
// set attributes that are driven by data
divs
    .attr("id", function(d) { return d.label; })
    .each(function(d) { // 'd' is datum from parent div
        var s = d3.select(this);
        s.select("p")
            .text(d.text);
        s.select("img")
            .attr("src", d.img);
    });

// EXIT
divs.exit().remove();


One way of doing it without the use of each:

// DATA JOIN
var divs = d3.select("body").selectAll("div")
    .data(data);

// UPDATE OLD

// ENTER
// create the elements and set attributes not related to bound data
var divsenter = divs.enter().append("div");
divsenter.append("p");
divsenter.append("img");

// ENTER + UPDATE
// set data related attributes
divs.attr("id", function(d) { return d.label; });
divs.select("p") // 'select' copies data from parent div to child p (not relevant in this case because datum is an object)
    .text(function(d) { return d.text; });
divs.select("img") 
    .attr("src", function(d) { return d.img; });

// EXIT
divs.exit().remove();


Regarding the use of position instead of classes. I guess, if within the each function you select all the text elements like:

var texts = d3.select(this).selectAll('text');

Then d3.select(texts[0][0]) is the selection that corresponds to your first element and d3.select(texts[0][1]) to the second text element.

Tore

Ian Johnson

unread,
Jan 10, 2013, 5:22:42 PM1/10/13
to d3...@googlegroups.com
In your example both code snippets do the exact same thing, except using .each wastes code and resources (you already have the selections, so no need to explicitly reselect). i don't see any pros to it. but I am open to being convinced otherwise.

here is an example of updating the inner text elements with the update pattern on new data

Reply all
Reply to author
Forward
0 new messages