kjo
kjo

Reputation: 35311

Looking for selectAll-like operation that will include calling object in the selection (whenever it matches the selector)

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

Answers (2)

altocumulus
altocumulus

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

Cool Blue
Cool Blue

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

Related Questions