diff --git a/README.md b/README.md index 94296d15..71362fe9 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,11 @@ A reference to the data associated with this node, as specified to the [construc # node.depth -The depth of the node: zero for the root node, and increasing by one for each subsequent generation. +The depth of the node: zero for the root node, and increasing by one for each descendant generation. + +# node.height + +The height of the node: zero for any leaf node, and increasing by one for each ancestor generation. # node.parent @@ -140,7 +144,6 @@ Similarly, to sort nodes by descending height (greatest distance from any descen ```js root .sum(function(d) { return d.value; }) - .eachBefore(function(d) { var h = 0; do d.height = h; while ((d = d.parent) && (d.height < ++h)); }) .sort(function(a, b) { return b.height - a.height || b.value - a.value; }); ``` @@ -495,6 +498,7 @@ This returns: { "id": "Eve", "depth": 0, + "height": 2, "data": { "id": "Eve", "parentId": "" @@ -503,6 +507,7 @@ This returns: { "id": "Cain", "depth": 1, + "height": 0, "parent": [Circular], "data": { "id": "Cain", @@ -512,6 +517,7 @@ This returns: { "id": "Seth", "depth": 1, + "height": 1, "parent": [Circular], "data": { "id": "Seth", @@ -521,6 +527,7 @@ This returns: { "id": "Enos", "depth": 2, + "height": 0, "parent": [Circular], "data": { "id": "Enos", @@ -530,6 +537,7 @@ This returns: { "id": "Noam", "depth": 2, + "height": 0, "parent": [Circular], "data": { "id": "Noam", @@ -541,6 +549,7 @@ This returns: { "id": "Abel", "depth": 1, + "height": 0, "parent": [Circular], "data": { "id": "Abel", @@ -550,6 +559,7 @@ This returns: { "id": "Awan", "depth": 1, + "height": 1, "parent": [Circular], "data": { "id": "Awan", @@ -559,6 +569,7 @@ This returns: { "id": "Enoch", "depth": 2, + "height": 0, "parent": [Circular], "data": { "id": "Enoch", @@ -570,6 +581,7 @@ This returns: { "id": "Azura", "depth": 1, + "height": 0, "parent": [Circular], "data": { "id": "Azura", diff --git a/src/hierarchy/index.js b/src/hierarchy/index.js index 5d560856..f7c0edfb 100644 --- a/src/hierarchy/index.js +++ b/src/hierarchy/index.js @@ -30,18 +30,27 @@ export default function hierarchy(data) { } } - return root; + return root.eachBefore(computeHeight); } function node_copy() { - return hierarchy(this).eachBefore(function(node) { - node.data = node.data.data; - }); + return hierarchy(this).eachBefore(copyData); +} + +function copyData(node) { + node.data = node.data.data; +} + +export function computeHeight(node) { + var height = 0; + do node.height = height; + while ((node = node.parent) && (node.height < ++height)); } export function Node(data) { this.data = data; - this.depth = 0; + this.depth = + this.height = 0; this.parent = null; } diff --git a/src/stratify.js b/src/stratify.js index 10db8b2b..88e48d13 100644 --- a/src/stratify.js +++ b/src/stratify.js @@ -1,5 +1,5 @@ import {required} from "./accessors"; -import {Node} from "./hierarchy/index"; +import {Node, computeHeight} from "./hierarchy/index"; var keyPrefix = "$", // Protect against keys like “__proto__”. preroot = {depth: -1}; @@ -53,7 +53,7 @@ export default function() { if (!root) throw new Error("no root"); root.parent = preroot; - root.eachBefore(function(node) { node.depth = node.parent.depth + 1; --n; }); + root.eachBefore(function(node) { node.depth = node.parent.depth + 1; --n; }).eachBefore(computeHeight); root.parent = null; if (n > 0) throw new Error("cycle"); diff --git a/test/stratify-test.js b/test/stratify-test.js index b7481f1a..e1daaf83 100644 --- a/test/stratify-test.js +++ b/test/stratify-test.js @@ -20,16 +20,19 @@ tape("stratify(data) returns the root node", function(test) { test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 2, data: {id: "a"}, children: [ { id: "aa", depth: 1, + height: 1, data: {id: "aa", parentId: "a"}, children: [ { id: "aaa", depth: 2, + height: 0, data: {id: "aaa", parentId: "aa"} } ] @@ -37,6 +40,7 @@ tape("stratify(data) returns the root node", function(test) { { id: "ab", depth: 1, + height: 0, data: {id: "ab", parentId: "a"} } ] @@ -55,16 +59,19 @@ tape("stratify(data) does not require the data to be in topological order", func test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 2, data: {id: "a"}, children: [ { id: "aa", depth: 1, + height: 1, data: {id: "aa", parentId: "a"}, children: [ { id: "aaa", depth: 2, + height: 0, data: {id: "aaa", parentId: "aa"} } ] @@ -72,6 +79,7 @@ tape("stratify(data) does not require the data to be in topological order", func { id: "ab", depth: 1, + height: 0, data: {id: "ab", parentId: "a"} } ] @@ -90,21 +98,25 @@ tape("stratify(data) preserves the input order of siblings", function(test) { test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 2, data: {id: "a"}, children: [ { id: "ab", depth: 1, + height: 0, data: {id: "ab", parentId: "a"} }, { id: "aa", depth: 1, + height: 1, data: {id: "aa", parentId: "a"}, children: [ { id: "aaa", depth: 2, + height: 0, data: {id: "aaa", parentId: "aa"} } ] @@ -125,16 +137,19 @@ tape("stratify(data) treats an empty parentId as the root", function(test) { test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 2, data: {id: "a", parentId: ""}, children: [ { id: "aa", depth: 1, + height: 1, data: {id: "aa", parentId: "a"}, children: [ { id: "aaa", depth: 2, + height: 0, data: {id: "aaa", parentId: "aa"} } ] @@ -142,6 +157,7 @@ tape("stratify(data) treats an empty parentId as the root", function(test) { { id: "ab", depth: 1, + height: 0, data: {id: "ab", parentId: "a"} } ] @@ -159,16 +175,19 @@ tape("stratify(data) does not treat a falsy but non-empty parentId as the root", test.deepEqual(noparent(root), { id: "0", depth: 0, + height: 1, data: {id: 0, parentId: null}, children: [ { id: "1", depth: 1, + height: 0, data: {id: 1, parentId: 0} }, { id: "2", depth: 1, + height: 0, data: {id: 2, parentId: 0} } ] @@ -213,14 +232,17 @@ tape("stratify(data) allows the id to be undefined for leaf nodes", function(tes test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 1, data: {id: "a"}, children: [ { depth: 1, + height: 0, data: {parentId: "a"} }, { depth: 1, + height: 0, data: {parentId: "a"} } ] @@ -245,10 +267,12 @@ tape("stratify(data) allows the id to be undefined for leaf nodes", function(tes test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 1, data: {id: "a"}, children: [ { depth: 1, + height: 0, data: o } ] @@ -269,16 +293,19 @@ tape("stratify.id(id) observes the specified id function", function(test) { test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 2, data: {foo: "a"}, children: [ { id: "aa", depth: 1, + height: 1, data: {foo: "aa", parentId: "a"}, children: [ { id: "aaa", depth: 2, + height: 0, data: {foo: "aaa", parentId:"aa" } } ] @@ -286,6 +313,7 @@ tape("stratify.id(id) observes the specified id function", function(test) { { id: "ab", depth: 1, + height: 0, data: {foo: "ab", parentId:"a" } } ] @@ -313,16 +341,19 @@ tape("stratify.parentId(id) observes the specified parent id function", function test.deepEqual(noparent(root), { id: "a", depth: 0, + height: 2, data: {id: "a"}, children: [ { id: "aa", depth: 1, + height: 1, data: {id: "aa", foo: "a"}, children: [ { id: "aaa", depth: 2, + height: 0, data: {id: "aaa", foo: "aa"} } ] @@ -330,6 +361,7 @@ tape("stratify.parentId(id) observes the specified parent id function", function { id: "ab", depth: 1, + height: 0, data: {id: "ab", foo: "a"} } ] diff --git a/test/treemap/flare-test.js b/test/treemap/flare-test.js index 1d71b749..02dd62b3 100644 --- a/test/treemap/flare-test.js +++ b/test/treemap/flare-test.js @@ -50,6 +50,7 @@ function test(input, expected, tile) { delete node.parent; delete node.data; delete node._squarify; + delete node.height; if (node.children) node.children.forEach(visit); })(actual);