sjp14051
sjp14051

Reputation: 99

D3 selection and entry

I'm trying to create elements using d3.js by first selecting a tag using a unique identifier, in this case "mydat". However, the following code doesn't give me anything.

<body>
<div id="mydat"></div>
<script src="http://d3js.org/d3.v3.js"></script>
<script>
for (i=0; i<5; i++) {
d3.select("body").select("#mydat")
    .data(i.toString())
    .enter()
    .append("div")
    .text(String)
}
</script>
</body>

On the other hand, if I use the selection using:

d3.select("body").select("div")

and remove the first

<div id="mydat"></div>

then I get

<div>1</div><div>2</div>

etc. I am puzzled. Why doesn't the first method of selecting, i.e.

d3.select("body").select("#mydat")

identify the existing 'div' tag and put the child 'div' tags inside? Also, why does the second method of selecting not work if I do not remove the existing 'div id="mydat"'? Thanks.

Upvotes: 0

Views: 181

Answers (3)

Mike Precup
Mike Precup

Reputation: 4218

The correct way to do what you want is

var data = d3.range(0,5);

d3.select("body").select("#mydat")
    .selectAll("div")
    .data(data)
    .enter()
    .append("div")
    .text(function(d) { return d; })

The behavior you're encountering is a mix of two mistakes coming together in your code.

The first issue is that .data() is supposed to accept an array of data, and a string can be treated as an array of characters, so d3 breaks the strings apart into characters. For 0 <= i < 5, this isn't obvious, but if you change i to go into double digits, you'll see divs that look like this:

<div>1</div> //10
<div>0</div>
<div>1</div> //11
<div>1</div>
<div>1</div> //12
<div>2</div>

The fix is to remove the loop, and pass in an array of data instead. Loops are a rarity in d3, since it has so many powerful features to operate on groups of elements at once.

The second issue, and the reason your selections aren't working as expected, has to do with parent elements.

The implementation of enter.select is then specialized such that nodes are inserted into the group’s parent, replacing the placeholder. This is why it is critical to call selection.selectAll prior to a data join: it establishes the parent node for entering elements.

Source: Mike Bostock's explanation of selections

Basically, append will insert the elements into the element that's the parent node for the current selection. Since the current selection has never had a parent set with selectAll, you're running into weird behavior with parent nodes. Always set the parent node explicitly with a selectAll.

Upvotes: 2

Glenn
Glenn

Reputation: 5342

You don't need a loop with d3, in this case.

Try:

var data = d3.range(0,5);
var sel = d3.select("#mydat").selectAll("div.mydatItem").data(data);
sel.enter().append("div").attr("class", "mydatItem");
sel.text(function(d) { return d;};

This will create:

<div id="mydat">
    <div class="mydatItem">0</div>
    <div class="mydatItem">1</div>
    <div class="mydatItem">2</div>
    <div class="mydatItem">3</div>
    <div class="mydatItem">4</div>
</div>

select only selects a single element, whereas selectall will select all the matching elements. That is, select("div") will select the first div it finds, and selectAll("div") will select every div.

I'm not entirely clear why you're getting the output you see, but your ".data()" call should pass an array of data. You normally pass a non-array item as "data([item])".

Upvotes: 1

Ashima
Ashima

Reputation: 4824

Try this instead:

d3.select('#mydat') 

instead of this

   d3.select('body').select('#mydat')

Upvotes: -2

Related Questions