Skip to content

Commit

Permalink
Change selection.filter to preserve index.
Browse files Browse the repository at this point in the history
This makes it more useful in conjunction with selection.merge.
  • Loading branch information
mbostock committed Feb 22, 2016
1 parent 079c87e commit 0c252f1
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 13 deletions.
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -116,7 +116,7 @@ Unlike [*selection*.select](#selection_select), *selection*.selectAll does affec

<a name="selection_filter" href="#selection_filter">#</a> <i>selection</i>.<b>filter</b>(<i>filter</i>)

Filters the selection, returning a new selection that contains only the elements for which the specified *filter* is true. The *filter* may be specified either as a selector string or a function. If a function, it is evaluated for each selected element, in order, being passed the current datum `d` and index `i`, with the `this` context as the current DOM element. For example, to filter a selection of table rows to contain only even rows:
Filters the selection, returning a new selection that contains only the elements for which the specified *filter* is true. The returned filtered selection preserves the index and parents of this selection. The *filter* may be specified either as a selector string or a function. If a function, it is evaluated for each selected element, in order, being passed the current datum `d` and index `i`, with the `this` context as the current DOM element. For example, to filter a selection of table rows to contain only even rows:

```js
var even = d3.selectAll("tr").filter(":nth-child(even)");
Expand All @@ -142,11 +142,9 @@ var even = d3.selectAll("tr").select(function(d, i) { return i & 1 ? this : null

Note that the `:nth-child` pseudo-class is a one-based index rather than a zero-based index. Also, the above filter functions do not have precisely the same meaning as `:nth-child`; they rely on the selection index rather than the number of preceeding sibling elements in the DOM.

The returned selection may not preserve the index of the original selection, as some elements may be removed; you can use [*selection*.select](#selection_select) to preserve the index, if needed.

<a name="selection_merge" href="#selection_merge">#</a> <i>selection</i>.<b>merge</b>(<i>selection</i>)

Returns a new selection merging this selection with the specified *selection*. The returned selection has the same number of groups and the same parents as this selection. Any missing (null) elements in this selection are filled with the corresponding element from the specified *selection*, if present (not null).
Returns a new selection merging this selection with the specified *selection*. The returned selection has the same number of groups and the same parents as this selection. Any missing (null) elements in this selection are filled with the corresponding element, if present (not null), from the specified *selection*.

This method is commonly used to merge the [enter](#selection_enter) and [update](#selection_data) selections after a [data-join](#joining-data). After modifying the entering and updating elements separately, you can merge the two selections and perform operations on both without duplicate code. For example:

Expand All @@ -157,6 +155,8 @@ var circle = svg.selectAll("circle").data(data).style("fill", "blue"), // make u
circle.merge(circleEnter).style("stroke", "black"); // give updating OR entering circles a black stroke
```

This method is also useful for merging [filtered](#selection_filter) selections because a filtered selection retains the index structure of the originating selection. Note, however, that this method is not useful for concatenating arbitrary selections, as if this selection and the specified *selection* both have (non-null) elements at the same index, then this selection’s element is returned in the merged selection, and the specified *selection*’s element is ignored.

<a name="matcher" href="#matcher">#</a> d3.<b>matcher</b>(<i>selector</i>)

Given the specified *selector*, returns a function which returns true if `this` element [matches](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) the specified selector. This method is used internally by [*selection*.filter](#selection_filter). For example, this:
Expand Down
4 changes: 2 additions & 2 deletions src/selection/filter.js
Expand Up @@ -5,9 +5,9 @@ export default function(match) {
if (typeof match !== "function") match = matcher(match);

for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
subgroup.push(node);
subgroup[i] = node;
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions test/selection/filter-test.js
Expand Up @@ -12,7 +12,7 @@ tape("selection.filter(string) retains the selected elements that matches the se
var document = jsdom.jsdom("<h1><span id='one'></span><span id='two'></span></h1><h1><span id='three'></span><span id='four'></span></h1>"),
one = document.querySelector("#one"),
three = document.querySelector("#three");
test.deepEqual(d3.select(document).selectAll("span").filter("#one,#three"), {_groups: [[one, three]], _parents: [document]});
test.deepEqual(d3.select(document).selectAll("span").filter("#one,#three"), {_groups: [[one,, three, ]], _parents: [document]});
test.end();
});

Expand All @@ -22,7 +22,7 @@ tape("selection.filter(function) retains elements for which the given function r
two = document.querySelector("#two"),
three = document.querySelector("#three"),
four = document.querySelector("#four");
test.deepEqual(d3.selectAll([one, two, three, four]).filter(function(d, i) { return i & 1; }), {_groups: [[two, four]], _parents: [null]});
test.deepEqual(d3.selectAll([one, two, three, four]).filter(function(d, i) { return i & 1; }), {_groups: [[, two,, four]], _parents: [null]});
test.end();
});

Expand Down Expand Up @@ -69,12 +69,12 @@ tape("selection.filter(…) can filter elements when the originating selection i
test.end();
});

tape("selection.filter(…) skips missing originating elements and does not retain the original indexes", function(test) {
tape("selection.filter(…) skips missing originating elements and retains the original indexes", function(test) {
var document = jsdom.jsdom("<h1>hello</h1>"),
h1 = document.querySelector("h1");
test.deepEqual(d3.selectAll([, h1]).filter("*"), {_groups: [[h1]], _parents: [null]});
test.deepEqual(d3.selectAll([null, h1]).filter("*"), {_groups: [[h1]], _parents: [null]});
test.deepEqual(d3.selectAll([undefined, h1]).filter("*"), {_groups: [[h1]], _parents: [null]});
test.deepEqual(d3.selectAll([, h1]).filter("*"), {_groups: [[, h1]], _parents: [null]});
test.deepEqual(d3.selectAll([null, h1]).filter("*"), {_groups: [[, h1]], _parents: [null]});
test.deepEqual(d3.selectAll([undefined, h1]).filter("*"), {_groups: [[, h1]], _parents: [null]});
test.end();
});

Expand All @@ -84,6 +84,6 @@ tape("selection.filter(…) skips missing originating elements when the originat
two = document.querySelector("#two"),
three = document.querySelector("#three"),
four = document.querySelector("#four");
test.deepEqual(d3.selectAll([one, two]).selectAll("child").select(function(d, i) { return i & 1 ? this : null; }).filter("*"), {_groups: [[three], [four]], _parents: [one, two]});
test.deepEqual(d3.selectAll([one, two]).selectAll("child").select(function(d, i) { return i & 1 ? this : null; }).filter("*"), {_groups: [[, three], [, four]], _parents: [one, two]});
test.end();
});

0 comments on commit 0c252f1

Please sign in to comment.