Reputation: 1688
In the answer Mike posted here, he overviews three different ways to apply a change to a matched element based on a index or custom filter. I'm trying to clarify, hopefully for more people than just myself, the actual selections in these solutions.
So given a document with 6 SVG rects with class .bar
, we have these different selections and what they return:
d3.select(".bar"):
[Array[1]
0: rect.[object SVGAnimatedString]
length: 1
parentNode: html
__proto__: Array[0]
d3.selectAll(".bar"):
[Array[6]
0: rect.[object SVGAnimatedString]
1: rect.[object SVGAnimatedString]
2: rect.[object SVGAnimatedString]
3: rect.[object SVGAnimatedString]
4: rect.[object SVGAnimatedString]
5: rect.[object SVGAnimatedString]
length: 6
parentNode: html
__proto__: Array[0]
$(".bar"):
[
<rect class="dataBars" x="53.191489361702125" width="212.7659574468085" y="4.761904761904762" height="11.11111111111111"></rect>,
<rect class="dataBars" x="74.46808510638297" width="372.3404255319149" y="20.634920634920636" height="11.11111111111111"></rect>,
<rect class="dataBars" x="127.6595744680851" width="212.7659574468085" y="36.507936507936506" height="11.11111111111111"></rect>,
<rect class="dataBars" x="31.914893617021274" width="212.7659574468085" y="52.38095238095238" height="11.11111111111111"></rect>,
<rect class="dataBars" x="159.5744680851064" width="265.9574468085106" y="68.25396825396825" height="11.11111111111111"></rect>,
<rect class="dataBars" x="234.04255319148936" width="138.29787234042553" y="84.12698412698413" height="11.11111111111111"></rect>,
]
Now here's where it get's more tricky (for me at least), say I want to apply a style
to the 3rd rectangle, this rectangle can be found using
d3.selectAll(".bar")[0][2]
But if we want to then use the d3.selection.attr()
, that returns
TypeError: Property 'style' of object #<SVGRectElement> is not a function
But we can then wrap this selection
d3.select(d3.selectAll(".bars rect")[0][2]).style("fill", "red")
and this will work as expected.
Then, if we want to apply a filter, such as
filter(function (d) { return d === 5 || d === 15;}
the d3.selectAll(".bar")
must be used, and d3.select(d3.selectAll(".bar"))
will not work correctly.
I've read Mike's excellent tutorials and documentation on selections, but just when I think I have it figured out, something pops up and surprises me. So what is the difference between these selections, and how do I know which one to use when? Thank you very much, and sorry for the long post!
Upvotes: 5
Views: 4229
Reputation: 25954
I've tried to do this in the past and run into similar errors. Then I realized that I was not really following the intended API. The second that you start accessing selection elements by index, you're way off the beaten path.
So, if you wanted to style your third bar, you would do
d3.selectAll(".bar").style("color", function(d,i) { return i === 2 ? "red" : null; } )
And if your selection is one level more nested than that, make it function(d,i,j)
and similarly go from there. Etc.
Upvotes: 5
Reputation: 109232
In general, you shouldn't access elements of a returned selection by their indices, but rather filter or use a subselection. This way, you can apply .attr()
and .atyle()
without problems. The reason for this is that D3 doesn't return a "pure" array of DOM elements (like jquery does), but the elements in a wrapper that supports the D3 operations. It still behaves somewhat like an array though -- if you index into it, you get a "pure" DOM element. You can also give a DOM element to d3.select()
and it will generate the wrapper around this element that enables all the D3 stuff.
If you have a look at the documentation for filter()
, you'll find some examples on how to use it and subselections. You can use these techniques to get the third element of a selection for example. Using the .filter()
function only makes sense if you have bound data to the objects you're filtering, otherwise a subselection should do what you want.
Upvotes: 3