Reputation: 35311
The d3.js expression
d3.select(foo).selectAll(some_selector)
will return a selection comprising all the strict descendants of foo
that satisfy some_selector
.
But suppose that foo
itself satisfies some_selector
. How can I get it included in the resulting selection when this is the case?
The following naive solution to this problem
d3.select(foo.parentNode).selectAll(some_selector)
is incorrect, because, in general, the selection resulting from it will include any siblings of foo
that satisfy some_selector
!
IOW, I'm looking for a solution that is clearer, more concise, and less of a dirty hack than this (for example):
// temporarily tag all candidate elements, namely, foo and all its descendants
d3.select(foo).classed('yuk', true)
.selectAll('*').classed('yuk', true);
var parent = d3.select(foo.parentNode),
wanted = parent.selectAll(some_selector)
.filter('.yuk');
// undo the tagging
parent.selectAll('.yuk').classed('yuk', false);
Upvotes: 0
Views: 98
Reputation: 21578
Your question addresses the same issue as the other one you posted yesterday, although not being an exact duplicate. My answer to that one will work for this problem as well. I have adjusted my JSFiddle to allow for some filtering on the node and its descendants:
var selector = ".foo",
x = d3.select("#x"); // your parent node
// Get all children of node x regarding selector as NodeList and convert to Array.
var xAndDescendants = Array.prototype.slice.call(
x.node().querySelectorAll(selector)
);
// Add node x to the beginning if selector is true.
if (!(x = x.filter(selector)).empty())
xAndDescendants.unshift(x.node());
// Select resulting array via d3.js
var selection = d3.selectAll(xAndDescendants);
Upvotes: 2
Reputation: 6476
It's possible to avoid the class but its a bit complicated. Here is one solution (I'm sure there are simpler ways!)..
function init() {
d3.select(foo)
.selectAll('*')
.call(function () {
immediateFamily(this, function (selection) {
return selection
.style('padding', '3px')
.style('border', '1px solid green')
})
})
}
;(function () {
$(document).ready(init)
})()
function immediateFamily(selection, styling) {
styling(selection)
styling(d3.select(selection[0].parentNode))
}
The idea is to avoid repeating the styling clauses by putting them in an anonymous function in the selection chain and pass this, along with the this context, to a function that applies said styling to the the selection and the parent node of the first group in the selection.
To make it slightly more terse - and even less comprehensible...
function immediateFamily2(selection, styling) {
styling(d3.select(styling(selection)[0].parentNode))
}
To take it to it's ultimate and possibly most idiomatically correct conclusion...
;(function () {
$(document).ready(init2)
function init2() {
var foo = d3.select('tr').filter(':first-child')[0][0]
d3.select(foo)
.selectAll('*')
.call(immediateFamily, bar)
function bar(selection) {
selection
.style('margin', '10px 20px 10px 20px')
.style('outline', '1px solid green')
}
}
function immediateFamily(selection, styling) {
styling(this)
styling(d3.select(this[0].parentNode))
return this
}
})()
Of course it could be further generalised but you get the idea.
(This code runs fine, but feel free to insert your own semicolons!)
Upvotes: 0