Skip to content

Commit

Permalink
Merge pull request #155 from d3/two
Browse files Browse the repository at this point in the history
Accept iterables.
  • Loading branch information
Fil authored Aug 23, 2020
2 parents d6adccb + 48a5616 commit 6f7c5ed
Show file tree
Hide file tree
Showing 26 changed files with 368 additions and 173 deletions.
40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ A good hierarchical visualization facilitates rapid multiscale inference: micro-
If you use NPM, `npm install d3-hierarchy`. Otherwise, download the [latest release](https://github.com/d3/d3-hierarchy/releases/latest). You can also load directly from [d3js.org](https://d3js.org), either as a [standalone library](https://d3js.org/d3-hierarchy.v1.min.js) or as part of [D3](https://github.com/d3/d3). AMD, CommonJS, and vanilla environments are supported. In vanilla, a `d3` global is exported:

```html
<script src="https://d3js.org/d3-hierarchy.v1.min.js"></script>
<script src="https://d3js.org/d3-hierarchy.v2.min.js"></script>
<script>
var treemap = d3.treemap();
Expand Down Expand Up @@ -78,14 +78,24 @@ Constructs a root node from the specified hierarchical *data*. The specified *da
}
```

The specified *children* accessor function is invoked for each datum, starting with the root *data*, and must return an array of data representing the children, or null if the current datum has no children. If *children* is not specified, it defaults to:
The specified *children* accessor function is invoked for each datum, starting with the root *data*, and must return an iterable of data representing the children, if any. If the children accessor is not specified, it defaults to:

```js
function children(d) {
return d.children;
}
```

If *data* is a Map, it is implicitly converted to the entry [undefined, *data*], and the children accessor instead defaults to:

```js
function children(d) {
return Array.isArray(d) ? d[1] : null;
}
```

This allows you to pass the result of [d3.group](https://github.com/d3/d3-array/blob/master/README.md#group) or [d3.rollup](https://github.com/d3/d3-array/blob/master/README.md#rollup) to d3.hierarchy.

The returned node and each descendant has the following properties:

* *node*.data - the associated data, as specified to the [constructor](#hierarchy).
Expand All @@ -109,6 +119,10 @@ Returns the array of descendant nodes, starting with this node, then followed by

Returns the array of leaf nodes in traversal order; leaves are nodes with no children.

<a name="node_find" href="#node_find">#</a> <i>node</i>.<b>find</b>(<i>filter</i>) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/find.js)<!-- , [Examples](https://observablehq.com/@d3/d3-hierarchy) -->

Returns the first node in the hierarchy from this *node* for which the specified *filter* returns a truthy value. undefined if no such node is found.

<a name="node_path" href="#node_path">#</a> <i>node</i>.<b>path</b>(<i>target</i>) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/path.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)

Returns the shortest path through the hierarchy from this *node* to the specified *target* node. The path starts at this *node*, ascends to the least common ancestor of this *node* and the *target* node, and then descends to the *target* node. This is particularly useful for [hierarchical edge bundling](https://observablehq.com/@d3/hierarchical-edge-bundling).
Expand Down Expand Up @@ -174,17 +188,27 @@ root

You must call *node*.sort before invoking a hierarchical layout if you want the new sort order to affect the layout; see [*node*.sum](#node_sum) for an example.

<a name="node_each" href="#node_each">#</a> <i>node</i>.<b>each</b>(<i>function</i>) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/each.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
<a name="node_iterator" href="#node_iterator">#</a> <i>node</i>[<b>Symbol.iterator</b>]() [<>](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/iterator.js "Source")

Returns an iterator over the *node*’s descendants in breadth-first order. For example:

```js
for (const descendant of node) {
console.log(descendant);
}
```

<a name="node_each" href="#node_each">#</a> <i>node</i>.<b>each</b>(<i>function</i>[, <i>that</i>]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/each.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)

Invokes the specified *function* for *node* and each descendant in [breadth-first order](https://en.wikipedia.org/wiki/Breadth-first_search), such that a given *node* is only visited if all nodes of lesser depth have already been visited, as well as all preceding nodes of the same depth. The specified function is passed the current *node*.
Invokes the specified *function* for *node* and each descendant in [breadth-first order](https://en.wikipedia.org/wiki/Breadth-first_search), such that a given *node* is only visited if all nodes of lesser depth have already been visited, as well as all preceding nodes of the same depth. The specified function is passed the current *descendant*, the zero-based traversal *index*, and this *node*. If *that* is specified, it is the this context of the callback.

<a name="node_eachAfter" href="#node_eachAfter">#</a> <i>node</i>.<b>eachAfter</b>(<i>function</i>) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/eachAfter.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
<a name="node_eachAfter" href="#node_eachAfter">#</a> <i>node</i>.<b>eachAfter</b>(<i>function</i>[, <i>that</i>]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/eachAfter.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)

Invokes the specified *function* for *node* and each descendant in [post-order traversal](https://en.wikipedia.org/wiki/Tree_traversal#Post-order), such that a given *node* is only visited after all of its descendants have already been visited. The specified function is passed the current *node*.
Invokes the specified *function* for *node* and each descendant in [post-order traversal](https://en.wikipedia.org/wiki/Tree_traversal#Post-order), such that a given *node* is only visited after all of its descendants have already been visited. The specified function is passed the current *descendant*, the zero-based traversal *index*, and this *node*. If *that* is specified, it is the this context of the callback.

<a name="node_eachBefore" href="#node_eachBefore">#</a> <i>node</i>.<b>eachBefore</b>(<i>function</i>) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/eachBefore.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
<a name="node_eachBefore" href="#node_eachBefore">#</a> <i>node</i>.<b>eachBefore</b>(<i>function</i>[, <i>that</i>]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/eachBefore.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)

Invokes the specified *function* for *node* and each descendant in [pre-order traversal](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order), such that a given *node* is only visited after all of its ancestors have already been visited. The specified function is passed the current *node*.
Invokes the specified *function* for *node* and each descendant in [pre-order traversal](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order), such that a given *node* is only visited after all of its ancestors have already been visited. The specified function is passed the current *descendant*, the zero-based traversal *index*, and this *node*. If *that* is specified, it is the this context of the callback.

<a name="node_copy" href="#node_copy">#</a> <i>node</i>.<b>copy</b>() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/index.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "d3-hierarchy",
"version": "1.1.9",
"version": "2.0.0-rc.3",
"publishConfig": {
"tag": "next"
},
"description": "Layout algorithms for visualizing hierarchical data.",
"keywords": [
"d3",
Expand Down Expand Up @@ -40,7 +43,6 @@
"benchmark": "^2.1.4",
"d3-array": "1.2.0 - 2",
"d3-dsv": "1",
"d3-queue": "3",
"d3-random": "1.1.0 - 2",
"eslint": "6",
"rollup": "1",
Expand Down
6 changes: 5 additions & 1 deletion src/array.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export var slice = Array.prototype.slice;
export default function(x) {
return typeof x === "object" && "length" in x
? x // Array, TypedArray, NodeList, array-like
: Array.from(x); // Map, Set, iterable, string, or anything else
}

export function shuffle(array) {
var m = array.length,
Expand Down
6 changes: 1 addition & 5 deletions src/hierarchy/descendants.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
export default function() {
var nodes = [];
this.each(function(node) {
nodes.push(node);
});
return nodes;
return Array.from(this);
}
16 changes: 5 additions & 11 deletions src/hierarchy/each.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
export default function(callback) {
var node = this, current, next = [node], children, i, n;
do {
current = next.reverse(), next = [];
while (node = current.pop()) {
callback(node), children = node.children;
if (children) for (i = 0, n = children.length; i < n; ++i) {
next.push(children[i]);
}
}
} while (next.length);
export default function(callback, that) {
let index = -1;
for (const node of this) {
callback.call(that, node, ++index, this);
}
return this;
}
14 changes: 8 additions & 6 deletions src/hierarchy/eachAfter.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
export default function(callback) {
var node = this, nodes = [node], next = [], children, i, n;
export default function(callback, that) {
var node = this, nodes = [node], next = [], children, i, n, index = -1;
while (node = nodes.pop()) {
next.push(node), children = node.children;
if (children) for (i = 0, n = children.length; i < n; ++i) {
nodes.push(children[i]);
next.push(node);
if (children = node.children) {
for (i = 0, n = children.length; i < n; ++i) {
nodes.push(children[i]);
}
}
}
while (node = next.pop()) {
callback(node);
callback.call(that, node, ++index, this);
}
return this;
}
12 changes: 7 additions & 5 deletions src/hierarchy/eachBefore.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
export default function(callback) {
var node = this, nodes = [node], children, i;
export default function(callback, that) {
var node = this, nodes = [node], children, i, index = -1;
while (node = nodes.pop()) {
callback(node), children = node.children;
if (children) for (i = children.length - 1; i >= 0; --i) {
nodes.push(children[i]);
callback.call(that, node, ++index, this);
if (children = node.children) {
for (i = children.length - 1; i >= 0; --i) {
nodes.push(children[i]);
}
}
}
return this;
Expand Down
8 changes: 8 additions & 0 deletions src/hierarchy/find.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function(callback, that) {
let index = -1;
for (const node of this) {
if (callback.call(that, node, ++index, this)) {
return node;
}
}
}
30 changes: 21 additions & 9 deletions src/hierarchy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,37 @@ import node_count from "./count.js";
import node_each from "./each.js";
import node_eachBefore from "./eachBefore.js";
import node_eachAfter from "./eachAfter.js";
import node_find from "./find.js";
import node_sum from "./sum.js";
import node_sort from "./sort.js";
import node_path from "./path.js";
import node_ancestors from "./ancestors.js";
import node_descendants from "./descendants.js";
import node_leaves from "./leaves.js";
import node_links from "./links.js";
import node_iterator from "./iterator.js";

export default function hierarchy(data, children) {
if (data instanceof Map) {
data = [undefined, data];
if (children === undefined) children = mapChildren;
} else if (children === undefined) {
children = objectChildren;
}

var root = new Node(data),
valued = +data.value && (root.value = data.value),
node,
nodes = [root],
child,
childs,
i,
n;

if (children == null) children = defaultChildren;

while (node = nodes.pop()) {
if (valued) node.value = +node.data.value;
if ((childs = children(node.data)) && (n = childs.length)) {
node.children = new Array(n);
if ((childs = children(node.data)) && (n = (childs = Array.from(childs)).length)) {
node.children = childs;
for (i = n - 1; i >= 0; --i) {
nodes.push(child = node.children[i] = new Node(childs[i]));
nodes.push(child = childs[i] = new Node(childs[i]));
child.parent = node;
child.depth = node.depth + 1;
}
Expand All @@ -41,11 +46,16 @@ function node_copy() {
return hierarchy(this).eachBefore(copyData);
}

function defaultChildren(d) {
function objectChildren(d) {
return d.children;
}

function mapChildren(d) {
return Array.isArray(d) ? d[1] : null;
}

function copyData(node) {
if (node.data.value !== undefined) node.value = node.data.value;
node.data = node.data.data;
}

Expand All @@ -68,12 +78,14 @@ Node.prototype = hierarchy.prototype = {
each: node_each,
eachAfter: node_eachAfter,
eachBefore: node_eachBefore,
find: node_find,
sum: node_sum,
sort: node_sort,
path: node_path,
ancestors: node_ancestors,
descendants: node_descendants,
leaves: node_leaves,
links: node_links,
copy: node_copy
copy: node_copy,
[Symbol.iterator]: node_iterator
};
14 changes: 14 additions & 0 deletions src/hierarchy/iterator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default function*() {
var node = this, current, next = [node], children, i, n;
do {
current = next.reverse(), next = [];
while (node = current.pop()) {
yield node;
if (children = node.children) {
for (i = 0, n = children.length; i < n; ++i) {
next.push(children[i]);
}
}
}
} while (next.length);
}
6 changes: 3 additions & 3 deletions src/pack/enclose.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {shuffle, slice} from "../array.js";
import {shuffle} from "../array.js";

export default function(circles) {
var i = 0, n = (circles = shuffle(slice.call(circles))).length, B = [], p, e;
var i = 0, n = (circles = shuffle(Array.from(circles))).length, B = [], p, e;

while (i < n) {
p = circles[i];
Expand Down Expand Up @@ -47,7 +47,7 @@ function enclosesNot(a, b) {
}

function enclosesWeak(a, b) {
var dr = a.r - b.r + 1e-6, dx = b.x - a.x, dy = b.y - a.y;
var dr = a.r - b.r + Math.max(a.r, b.r, 1) * 1e-9, dx = b.x - a.x, dy = b.y - a.y;
return dr > 0 && dr * dr > dx * dx + dy * dy;
}

Expand Down
3 changes: 2 additions & 1 deletion src/pack/siblings.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import array from "../array.js";
import enclose from "./enclose.js";

function place(b, a, c) {
Expand Down Expand Up @@ -45,7 +46,7 @@ function Node(circle) {
}

export function packEnclose(circles) {
if (!(n = circles.length)) return 0;
if (!(n = (circles = array(circles)).length)) return 0;

var a, b, c, n, aa, ca, i, j, k, sj, sk;

Expand Down
32 changes: 17 additions & 15 deletions src/stratify.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {required} from "./accessors.js";
import {Node, computeHeight} from "./hierarchy/index.js";

var keyPrefix = "$", // Protect against keys like “__proto__”.
preroot = {depth: -1},
var preroot = {depth: -1},
ambiguous = {};

function defaultId(d) {
Expand All @@ -18,37 +17,40 @@ export default function() {
parentId = defaultParentId;

function stratify(data) {
var d,
var nodes = Array.from(data),
n = nodes.length,
d,
i,
n = data.length,
root,
parent,
node,
nodes = new Array(n),
nodeId,
nodeKey,
nodeByKey = {};
nodeByKey = new Map;

for (i = 0; i < n; ++i) {
d = data[i], node = nodes[i] = new Node(d);
d = nodes[i], node = nodes[i] = new Node(d);
if ((nodeId = id(d, i, data)) != null && (nodeId += "")) {
nodeKey = keyPrefix + (node.id = nodeId);
nodeByKey[nodeKey] = nodeKey in nodeByKey ? ambiguous : node;
nodeKey = node.id = nodeId;
nodeByKey.set(nodeKey, nodeByKey.has(nodeKey) ? ambiguous : node);
}
if ((nodeId = parentId(d, i, data)) != null && (nodeId += "")) {
node.parent = nodeId;
}
}

for (i = 0; i < n; ++i) {
node = nodes[i], nodeId = parentId(data[i], i, data);
if (nodeId == null || !(nodeId += "")) {
if (root) throw new Error("multiple roots");
root = node;
} else {
parent = nodeByKey[keyPrefix + nodeId];
node = nodes[i];
if (nodeId = node.parent) {
parent = nodeByKey.get(nodeId);
if (!parent) throw new Error("missing: " + nodeId);
if (parent === ambiguous) throw new Error("ambiguous: " + nodeId);
if (parent.children) parent.children.push(node);
else parent.children = [node];
node.parent = parent;
} else {
if (root) throw new Error("multiple roots");
root = node;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/treemap/binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ export default function(parent, x0, y0, x1, y1) {
valueRight = value - valueLeft;

if ((x1 - x0) > (y1 - y0)) {
var xk = (x0 * valueRight + x1 * valueLeft) / value;
var xk = value ? (x0 * valueRight + x1 * valueLeft) / value : x1;
partition(i, k, valueLeft, x0, y0, xk, y1);
partition(k, j, valueRight, xk, y0, x1, y1);
} else {
var yk = (y0 * valueRight + y1 * valueLeft) / value;
var yk = value ? (y0 * valueRight + y1 * valueLeft) / value : y1;
partition(i, k, valueLeft, x0, y0, x1, yk);
partition(k, j, valueRight, x0, yk, x1, y1);
}
Expand Down
Loading

0 comments on commit 6f7c5ed

Please sign in to comment.