Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 filter selection not working?

Tags:

d3.js

Either I am not using d3's selection.filter correctly, or it is buggy. I can distill the issue to a few lines. I'm in the Chrome debugger with d3 loaded. Let's start with an empty selection

d3.selectAll("nonexistant").empty()
> true

and bind some data to it.

d3.selectAll("nonexistant").data([1,2,3,4])
> [Array[4]]

Good, so it has size four. Let's check with selection.size:

d3.selectAll("nonexistant").data([1,2,3,4]).size()
> 0

Hmm, I guess that's because there are no DOM elements yet the update selection is empty since there were no previous elements. So let's make access the enter selection.

d3.selectAll("nonexistant").data([1,2,3,4]).enter()
> [Array[4]]
d3.selectAll("nonexistant").data([1,2,3,4]).enter().size()
> TypeError: undefined is not a function
d3.selectAll("nonexistant").data([1,2,3,4]).enter().append("p").size()
> 4

Not sure why the enter selection causes an error, (UPDATE: Fixed in v3.4.12) but anyway, if we try filtering using the example function in the docs,

function odds(d, i) { return i & 1; }
d3.selectAll("nonexistant").data([1,2,3,4]).filter(odds);
> [Array[0]]
d3.selectAll("nonexistant").data([1,2,3,4]).enter().filter(odds);
> []
d3.selectAll("nonexistant").data([1,2,3,4]).enter().append("p").filter(odds)
> [Array[2]]

Why is it silently filtering out all elements when there are no DOM elements bound? It does seem to be working when I already have DOM elements. But that feels pretty useless, since I don't want to create elements for data I'm discarding. Maybe if I put the filter earlier?

d3.selectAll("nonexistant").data([1,2,3,4]).filter(odds).enter().append("p").size()
> TypeError: undefined is not a function
d3.selectAll("nonexistant").data([1,2,3,4]).enter().filter(odds).append("p").size()
> TypeError: undefined is not a function

Nope. It seems the way to go is with JS's native filter on arrays:

d3.selectAll("nonexistant").data([1,2,3,4].filter(odds)).enter().append("p").size()
> 2

The d3 docs do not seem to differentiate between selections that have DOM elements bound and those that do not. It seems that I should be able to stick filter anywhere in my method chain (and call size on any selection), and get the correct result without a type error. Granted, filter also supports CSS selectors that will require DOM elements, but I'm not using them here.

What I want to know: There is a mismatch between what d3 is doing and what I expect. To what extent am I harboring misconceptions about selections and what operations are valid on them? To what extent is the documentation unclear? Does any of this behavior qualify as a bug?

like image 374
mgold Avatar asked Aug 16 '14 21:08

mgold


Video Answer


1 Answers

From the documentation of the .enter() method:

... the entering selection only defines append, insert, select and call operators; you must use these operators to instantiate the entering nodes before modifying any content. (Enter selections also support empty to check if they are empty.)

Calling anything else doesn't produce useful results. Whether or not that's a bug, a side-effect or a feature is perhaps debatable. In almost all cases, it doesn't create any barriers, except maybe if you need to know this selection's size() to find out how many datums from the array you pass to data() didn't already have elements created.

Once you call append() on the entering selection though, it behaves well, like any normal selection. In fact, append() is returning a new selection, so it !== the return value of enter().

That's when you can also check the size() of this selection, so really it only counts as an issue if you needed to know the size PRIOR to calling append().

You're correct that using the native array filter is the solution IF you don't need to even append elements where odds(d) == false.

Filter is useful when you've already created the DOM nodes (e.g. <p>s) that are bound to [1,2,3,4], and (e.g. in an event handler, when user clicks a "highlight all odds" button ) you call

d3.selectAll('p').filter(odds).css('color', 'red')

BTW, that was a really well written question.

like image 196
meetamit Avatar answered Oct 01 '22 19:10

meetamit