d3 filter selection not working?

Refresh

December 2018

Views

4k time

12

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?

1 answers

7

Из документации по .enter()методу:

... выбор ввода только определяет append, insert, selectи callоператор; Вы должны использовать эти операторы для создания экземпляра въезжающих узлов перед изменением любого содержания. (Enter выбор также поддержка , emptyчтобы проверить , если они пусты.)

Вызов ничего другого не дает полезные результаты. Независимо от того или нет , что это ошибка, побочный эффект или особенность, возможно , спорно. Почти во всех случаях, это не создает каких - либо препятствий, за исключением , может быть , если вам нужно знать этот выбор - х , size()чтобы узнать, сколько датумов из массива вы передаете data()уже не было элементов , созданных.

После того, как вы звоните append()на входе выбора , хотя, он ведет себя хорошо, как и любой нормальный отбор. На самом деле, append()возвращается новый выбор, так что !==возвращаемое значение enter().

Это когда вы можете также проверить size()этот выбор, так на самом деле он рассчитывает только как вопрос , если вам необходимо знать размер до вызова append().

Вы правы , что использование родного массива filterявляется решением , если вам не нужно даже добавлять элементы где odds(d) == false.

Фильтр полезен , когда вы уже создали DOM узлы (например , <p>s), которые связаны с [1,2,3,4], и (например , в обработчике событий, когда пользователь нажимает на кнопку «выделить все шансы») вы звоните

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

Кстати, это было действительно хорошо написано вопрос.