├── img
├── pack.png
├── tree.png
├── cluster.png
├── treemap.png
├── partition.png
└── stratify.png
├── .gitignore
├── src
├── hierarchy
│ ├── descendants.js
│ ├── ancestors.js
│ ├── sort.js
│ ├── each.js
│ ├── find.js
│ ├── leaves.js
│ ├── links.js
│ ├── sum.js
│ ├── count.js
│ ├── eachBefore.js
│ ├── iterator.js
│ ├── eachAfter.js
│ ├── path.js
│ └── index.js
├── constant.js
├── treemap
│ ├── round.js
│ ├── sliceDice.js
│ ├── dice.js
│ ├── slice.js
│ ├── resquarify.js
│ ├── binary.js
│ ├── squarify.js
│ └── index.js
├── accessors.js
├── array.js
├── index.js
├── partition.js
├── stratify.js
├── pack
│ ├── index.js
│ ├── enclose.js
│ └── siblings.js
├── cluster.js
└── tree.js
├── test
├── treemap
│ ├── round.js
│ ├── binary-test.js
│ ├── dice-test.js
│ ├── slice-test.js
│ ├── sliceDice-test.js
│ ├── flare-test.js
│ ├── resquarify-test.js
│ ├── squarify-test.js
│ └── index-test.js
├── hierarchy
│ ├── copy-test.js
│ ├── find-test.js
│ ├── links-test.js
│ ├── index-test.js
│ └── each-test.js
├── data
│ ├── simple3.json
│ ├── simple2.json
│ ├── simple.json
│ ├── flare.csv
│ ├── flare.json
│ └── flare-pack.json
├── pack
│ ├── find-bugs.js
│ ├── find-enclose-bugs.js
│ ├── flare-test.js
│ ├── find-place-bugs.js
│ ├── siblings-test.js
│ └── bench-enclose.js
└── stratify-test.js
├── .eslintrc.json
├── d3-hierarchy.sublime-project
├── rollup.config.js
├── LICENSE
├── package.json
└── README.md
/img/pack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asg017/d3-hierarchy/master/img/pack.png
--------------------------------------------------------------------------------
/img/tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asg017/d3-hierarchy/master/img/tree.png
--------------------------------------------------------------------------------
/img/cluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asg017/d3-hierarchy/master/img/cluster.png
--------------------------------------------------------------------------------
/img/treemap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asg017/d3-hierarchy/master/img/treemap.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sublime-workspace
2 | .DS_Store
3 | dist/
4 | node_modules
5 | npm-debug.log
6 |
--------------------------------------------------------------------------------
/img/partition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asg017/d3-hierarchy/master/img/partition.png
--------------------------------------------------------------------------------
/img/stratify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/asg017/d3-hierarchy/master/img/stratify.png
--------------------------------------------------------------------------------
/src/hierarchy/descendants.js:
--------------------------------------------------------------------------------
1 | export default function() {
2 | return Array.from(this);
3 | }
4 |
--------------------------------------------------------------------------------
/src/constant.js:
--------------------------------------------------------------------------------
1 | export function constantZero() {
2 | return 0;
3 | }
4 |
5 | export default function(x) {
6 | return function() {
7 | return x;
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/hierarchy/ancestors.js:
--------------------------------------------------------------------------------
1 | export default function() {
2 | var node = this, nodes = [node];
3 | while (node = node.parent) {
4 | nodes.push(node);
5 | }
6 | return nodes;
7 | }
8 |
--------------------------------------------------------------------------------
/src/hierarchy/sort.js:
--------------------------------------------------------------------------------
1 | export default function(compare) {
2 | return this.eachBefore(function(node) {
3 | if (node.children) {
4 | node.children.sort(compare);
5 | }
6 | });
7 | }
8 |
--------------------------------------------------------------------------------
/src/hierarchy/each.js:
--------------------------------------------------------------------------------
1 | export default function(callback, that) {
2 | let index = -1;
3 | for (const node of this) {
4 | callback.call(that, node, ++index, this);
5 | }
6 | return this;
7 | }
8 |
--------------------------------------------------------------------------------
/src/treemap/round.js:
--------------------------------------------------------------------------------
1 | export default function(node) {
2 | node.x0 = Math.round(node.x0);
3 | node.y0 = Math.round(node.y0);
4 | node.x1 = Math.round(node.x1);
5 | node.y1 = Math.round(node.y1);
6 | }
7 |
--------------------------------------------------------------------------------
/src/accessors.js:
--------------------------------------------------------------------------------
1 | export function optional(f) {
2 | return f == null ? null : required(f);
3 | }
4 |
5 | export function required(f) {
6 | if (typeof f !== "function") throw new Error;
7 | return f;
8 | }
9 |
--------------------------------------------------------------------------------
/src/hierarchy/find.js:
--------------------------------------------------------------------------------
1 | export default function(callback, that) {
2 | let index = -1;
3 | for (const node of this) {
4 | if (callback.call(that, node, ++index, this)) {
5 | return node;
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/treemap/sliceDice.js:
--------------------------------------------------------------------------------
1 | import dice from "./dice.js";
2 | import slice from "./slice.js";
3 |
4 | export default function(parent, x0, y0, x1, y1) {
5 | (parent.depth & 1 ? slice : dice)(parent, x0, y0, x1, y1);
6 | }
7 |
--------------------------------------------------------------------------------
/src/hierarchy/leaves.js:
--------------------------------------------------------------------------------
1 | export default function() {
2 | var leaves = [];
3 | this.eachBefore(function(node) {
4 | if (!node.children) {
5 | leaves.push(node);
6 | }
7 | });
8 | return leaves;
9 | }
10 |
--------------------------------------------------------------------------------
/test/treemap/round.js:
--------------------------------------------------------------------------------
1 | module.exports = function(d) {
2 | return {
3 | x0: round(d.x0),
4 | y0: round(d.y0),
5 | x1: round(d.x1),
6 | y1: round(d.y1)
7 | };
8 | };
9 |
10 | function round(x) {
11 | return Math.round(x * 100) / 100;
12 | }
13 |
--------------------------------------------------------------------------------
/src/hierarchy/links.js:
--------------------------------------------------------------------------------
1 | export default function() {
2 | var root = this, links = [];
3 | root.each(function(node) {
4 | if (node !== root) { // Don’t include the root’s parent, if any.
5 | links.push({source: node.parent, target: node});
6 | }
7 | });
8 | return links;
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "parserOptions": {
4 | "sourceType": "module",
5 | "ecmaVersion": 8
6 | },
7 | "env": {
8 | "es6": true,
9 | "node": true,
10 | "browser": true
11 | },
12 | "rules": {
13 | "no-cond-assign": 0
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/hierarchy/copy-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3 = require("../../");
3 |
4 | tape("node.copy() copies values", function(test) {
5 | var root = d3.hierarchy({id: "root", children: [{id: "a"}, {id: "b", children: [{id: "ba"}]}]}).count();
6 | test.equal(root.copy().value, 2);
7 | test.end();
8 | });
9 |
--------------------------------------------------------------------------------
/src/hierarchy/sum.js:
--------------------------------------------------------------------------------
1 | export default function(value) {
2 | return this.eachAfter(function(node) {
3 | var sum = +value(node.data) || 0,
4 | children = node.children,
5 | i = children && children.length;
6 | while (--i >= 0) sum += children[i].value;
7 | node.value = sum;
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/src/hierarchy/count.js:
--------------------------------------------------------------------------------
1 | function count(node) {
2 | var sum = 0,
3 | children = node.children,
4 | i = children && children.length;
5 | if (!i) sum = 1;
6 | else while (--i >= 0) sum += children[i].value;
7 | node.value = sum;
8 | }
9 |
10 | export default function() {
11 | return this.eachAfter(count);
12 | }
13 |
--------------------------------------------------------------------------------
/test/data/simple3.json:
--------------------------------------------------------------------------------
1 | {
2 | "children": [
3 | {
4 | "foo": 6
5 | },
6 | {
7 | "foo": 6
8 | },
9 | {
10 | "foo": 4
11 | },
12 | {
13 | "foo": 3
14 | },
15 | {
16 | "foo": 2
17 | },
18 | {
19 | "foo": 2
20 | },
21 | {
22 | "foo": 1
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/test/data/simple2.json:
--------------------------------------------------------------------------------
1 | {
2 | "children": [
3 | {
4 | "value": 6
5 | },
6 | {
7 | "value": 6
8 | },
9 | {
10 | "value": 4
11 | },
12 | {
13 | "value": 3
14 | },
15 | {
16 | "value": 2
17 | },
18 | {
19 | "value": 2
20 | },
21 | {
22 | "value": 1
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/treemap/dice.js:
--------------------------------------------------------------------------------
1 | export default function(parent, x0, y0, x1, y1) {
2 | var nodes = parent.children,
3 | node,
4 | i = -1,
5 | n = nodes.length,
6 | k = parent.value && (x1 - x0) / parent.value;
7 |
8 | while (++i < n) {
9 | node = nodes[i], node.y0 = y0, node.y1 = y1;
10 | node.x0 = x0, node.x1 = x0 += node.value * k;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/treemap/slice.js:
--------------------------------------------------------------------------------
1 | export default function(parent, x0, y0, x1, y1) {
2 | var nodes = parent.children,
3 | node,
4 | i = -1,
5 | n = nodes.length,
6 | k = parent.value && (y1 - y0) / parent.value;
7 |
8 | while (++i < n) {
9 | node = nodes[i], node.x0 = x0, node.x1 = x1;
10 | node.y0 = y0, node.y1 = y0 += node.value * k;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/hierarchy/eachBefore.js:
--------------------------------------------------------------------------------
1 | export default function(callback, that) {
2 | var node = this, nodes = [node], children, i, index = -1;
3 | while (node = nodes.pop()) {
4 | callback.call(that, node, ++index, this);
5 | if (children = node.children) {
6 | for (i = children.length - 1; i >= 0; --i) {
7 | nodes.push(children[i]);
8 | }
9 | }
10 | }
11 | return this;
12 | }
13 |
--------------------------------------------------------------------------------
/d3-hierarchy.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": ".",
5 | "file_exclude_patterns": ["*.sublime-workspace"],
6 | "folder_exclude_patterns": ["dist"]
7 | }
8 | ],
9 | "build_systems": [
10 | {
11 | "name": "yarn test",
12 | "cmd": ["yarn", "test"],
13 | "file_regex": "\\((...*?):([0-9]*):([0-9]*)\\)",
14 | "working_dir": "$project_path"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/src/hierarchy/iterator.js:
--------------------------------------------------------------------------------
1 | export default function*() {
2 | var node = this, current, next = [node], children, i, n;
3 | do {
4 | current = next.reverse(), next = [];
5 | while (node = current.pop()) {
6 | yield node;
7 | if (children = node.children) {
8 | for (i = 0, n = children.length; i < n; ++i) {
9 | next.push(children[i]);
10 | }
11 | }
12 | }
13 | } while (next.length);
14 | }
15 |
--------------------------------------------------------------------------------
/src/hierarchy/eachAfter.js:
--------------------------------------------------------------------------------
1 | export default function(callback, that) {
2 | var node = this, nodes = [node], next = [], children, i, n, index = -1;
3 | while (node = nodes.pop()) {
4 | next.push(node);
5 | if (children = node.children) {
6 | for (i = 0, n = children.length; i < n; ++i) {
7 | nodes.push(children[i]);
8 | }
9 | }
10 | }
11 | while (node = next.pop()) {
12 | callback.call(that, node, ++index, this);
13 | }
14 | return this;
15 | }
16 |
--------------------------------------------------------------------------------
/src/array.js:
--------------------------------------------------------------------------------
1 | export default function(x) {
2 | return typeof x === "object" && "length" in x
3 | ? x // Array, TypedArray, NodeList, array-like
4 | : Array.from(x); // Map, Set, iterable, string, or anything else
5 | }
6 |
7 | export function shuffle(array) {
8 | var m = array.length,
9 | t,
10 | i;
11 |
12 | while (m) {
13 | i = Math.random() * m-- | 0;
14 | t = array[m];
15 | array[m] = array[i];
16 | array[i] = t;
17 | }
18 |
19 | return array;
20 | }
21 |
--------------------------------------------------------------------------------
/test/data/simple.json:
--------------------------------------------------------------------------------
1 | {
2 | "children": [
3 | {
4 | "children": [
5 | {
6 | "value": 1
7 | },
8 | {
9 | "value": 2
10 | },
11 | {
12 | "value": 3
13 | }
14 | ]
15 | },
16 | {
17 | "children": [
18 | {
19 | "value": 4
20 | },
21 | {
22 | "value": 3
23 | },
24 | {
25 | "value": 2
26 | }
27 | ]
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/test/hierarchy/find-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3 = require("../../");
3 |
4 | tape("node.find() finds nodes", function(test) {
5 | var root = d3.hierarchy({id: "root", children: [{id: "a"}, {id: "b", children: [{id: "ba"}]}]}).count();
6 |
7 | test.equal(root.find(function(d) { return d.data.id == "b"; }).data.id, "b");
8 | test.equal(root.find(function(d, i) { return i == 0; }).data.id, "root");
9 | test.equal(root.find(function(d, i, e) { return d !== e; }).data.id, "a");
10 | test.end();
11 | });
12 |
--------------------------------------------------------------------------------
/test/hierarchy/links-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../");
3 |
4 | tape("node.links() returns an array of {source, target}", function(test) {
5 | var root = d3_hierarchy.hierarchy({id: "root", children: [{id: "a"}, {id: "b", children: [{id: "ba"}]}]}),
6 | a = root.children[0],
7 | b = root.children[1],
8 | ba = root.children[1].children[0];
9 | test.deepEqual(root.links(), [
10 | {source: root, target: a},
11 | {source: root, target: b},
12 | {source: b, target: ba}
13 | ]);
14 | test.end();
15 | });
16 |
--------------------------------------------------------------------------------
/src/hierarchy/path.js:
--------------------------------------------------------------------------------
1 | export default function(end) {
2 | var start = this,
3 | ancestor = leastCommonAncestor(start, end),
4 | nodes = [start];
5 | while (start !== ancestor) {
6 | start = start.parent;
7 | nodes.push(start);
8 | }
9 | var k = nodes.length;
10 | while (end !== ancestor) {
11 | nodes.splice(k, 0, end);
12 | end = end.parent;
13 | }
14 | return nodes;
15 | }
16 |
17 | function leastCommonAncestor(a, b) {
18 | if (a === b) return a;
19 | var aNodes = a.ancestors(),
20 | bNodes = b.ancestors(),
21 | c = null;
22 | a = aNodes.pop();
23 | b = bNodes.pop();
24 | while (a === b) {
25 | c = a;
26 | a = aNodes.pop();
27 | b = bNodes.pop();
28 | }
29 | return c;
30 | }
31 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export {default as cluster} from "./cluster.js";
2 | export {default as hierarchy} from "./hierarchy/index.js";
3 | export {default as pack} from "./pack/index.js";
4 | export {default as packSiblings} from "./pack/siblings.js";
5 | export {default as packEnclose} from "./pack/enclose.js";
6 | export {default as partition} from "./partition.js";
7 | export {default as stratify} from "./stratify.js";
8 | export {default as tree} from "./tree.js";
9 | export {default as treemap} from "./treemap/index.js";
10 | export {default as treemapBinary} from "./treemap/binary.js";
11 | export {default as treemapDice} from "./treemap/dice.js";
12 | export {default as treemapSlice} from "./treemap/slice.js";
13 | export {default as treemapSliceDice} from "./treemap/sliceDice.js";
14 | export {default as treemapSquarify} from "./treemap/squarify.js";
15 | export {default as treemapResquarify} from "./treemap/resquarify.js";
16 |
--------------------------------------------------------------------------------
/test/hierarchy/index-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3 = require("../../");
3 |
4 | tape("d3.hierarchy(data, children) supports iterable children", function(test) {
5 | var root = d3.hierarchy({id: "root", children: new Set([{id: "a"}, {id: "b", children: new Set([{id: "ba"}])}])}),
6 | a = root.children[0],
7 | b = root.children[1],
8 | ba = root.children[1].children[0];
9 | test.deepEqual(root.links(), [
10 | {source: root, target: a},
11 | {source: root, target: b},
12 | {source: b, target: ba}
13 | ]);
14 | test.end();
15 | });
16 |
17 | tape("d3.hierarchy(data, children) ignores non-iterable children", function(test) {
18 | var root = d3.hierarchy({id: "root", children: [{id: "a", children: null}, {id: "b", children: 42}]}),
19 | a = root.children[0],
20 | b = root.children[1];
21 | test.deepEqual(root.links(), [
22 | {source: root, target: a},
23 | {source: root, target: b}
24 | ]);
25 | test.end();
26 | });
27 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import {terser} from "rollup-plugin-terser";
2 | import * as meta from "./package.json";
3 |
4 | const config = {
5 | input: "src/index.js",
6 | external: Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)),
7 | output: {
8 | file: `dist/${meta.name}.js`,
9 | name: "d3",
10 | format: "umd",
11 | indent: false,
12 | extend: true,
13 | banner: `// ${meta.homepage} v${meta.version} Copyright ${(new Date).getFullYear()} ${meta.author.name}`,
14 | globals: Object.assign({}, ...Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)).map(key => ({[key]: "d3"})))
15 | },
16 | plugins: []
17 | };
18 |
19 | export default [
20 | config,
21 | {
22 | ...config,
23 | output: {
24 | ...config.output,
25 | file: `dist/${meta.name}.min.js`
26 | },
27 | plugins: [
28 | ...config.plugins,
29 | terser({
30 | output: {preamble: config.output.banner},
31 | mangle: {reserved: ["Node"]}
32 | })
33 | ]
34 | }
35 | ];
36 |
--------------------------------------------------------------------------------
/test/pack/find-bugs.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | var d3 = Object.assign({}, require("../../"), require("d3-random"));
4 |
5 | var n = 0, r = d3.randomLogNormal(4);
6 |
7 | while (true) {
8 | if (!(n % 100)) process.stdout.write(".");
9 | if (!(n % 10000)) process.stdout.write("\n" + n + " ");
10 | ++n;
11 | var radii = new Array(20).fill().map(r).map(Math.ceil);
12 | try {
13 | if (intersectsAny(d3.packSiblings(radii.map(r => ({r: r}))))) {
14 | throw new Error("overlap");
15 | }
16 | } catch (error) {
17 | process.stdout.write("\n");
18 | process.stdout.write(JSON.stringify(radii));
19 | process.stdout.write("\n");
20 | throw error;
21 | }
22 | }
23 |
24 | function intersectsAny(circles) {
25 | for (var i = 0, n = circles.length; i < n; ++i) {
26 | for (var j = i + 1, ci = circles[i], cj; j < n; ++j) {
27 | if (intersects(ci, cj = circles[j])) {
28 | return true;
29 | }
30 | }
31 | }
32 | return false;
33 | }
34 |
35 | function intersects(a, b) {
36 | var dr = a.r + b.r - 1e-6, dx = b.x - a.x, dy = b.y - a.y;
37 | return dr * dr > dx * dx + dy * dy;
38 | }
39 |
--------------------------------------------------------------------------------
/src/treemap/resquarify.js:
--------------------------------------------------------------------------------
1 | import treemapDice from "./dice.js";
2 | import treemapSlice from "./slice.js";
3 | import {phi, squarifyRatio} from "./squarify.js";
4 |
5 | export default (function custom(ratio) {
6 |
7 | function resquarify(parent, x0, y0, x1, y1) {
8 | if ((rows = parent._squarify) && (rows.ratio === ratio)) {
9 | var rows,
10 | row,
11 | nodes,
12 | i,
13 | j = -1,
14 | n,
15 | m = rows.length,
16 | value = parent.value;
17 |
18 | while (++j < m) {
19 | row = rows[j], nodes = row.children;
20 | for (i = row.value = 0, n = nodes.length; i < n; ++i) row.value += nodes[i].value;
21 | if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += (y1 - y0) * row.value / value : y1);
22 | else treemapSlice(row, x0, y0, value ? x0 += (x1 - x0) * row.value / value : x1, y1);
23 | value -= row.value;
24 | }
25 | } else {
26 | parent._squarify = rows = squarifyRatio(ratio, parent, x0, y0, x1, y1);
27 | rows.ratio = ratio;
28 | }
29 | }
30 |
31 | resquarify.ratio = function(x) {
32 | return custom((x = +x) > 1 ? x : 1);
33 | };
34 |
35 | return resquarify;
36 | })(phi);
37 |
--------------------------------------------------------------------------------
/test/pack/find-enclose-bugs.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | var d3 = Object.assign({}, require("../../"), require("d3-array"), require("d3-random"));
4 |
5 | var n = 0,
6 | m = 1000,
7 | r = d3.randomLogNormal(10),
8 | x = d3.randomUniform(0, 100),
9 | y = x;
10 |
11 | while (true) {
12 | if (!(n % 10)) process.stdout.write(".");
13 | if (!(n % 1000)) process.stdout.write("\n" + n + " ");
14 | ++n;
15 | var circles = new Array(20).fill().map(() => ({r: r(), x: x(), y: y()})), circles2,
16 | enclose = d3.packEnclose(circles), enclose2;
17 | if (circles.some(circle => !encloses(enclose, circle))) {
18 | console.log(JSON.stringify(circles));
19 | }
20 | for (var i = 0; i < m; ++i) {
21 | if (!equals(enclose, enclose2 = d3.packEnclose(circles2 = d3.shuffle(circles.slice())))) {
22 | console.log(JSON.stringify(enclose));
23 | console.log(JSON.stringify(enclose2));
24 | console.log(JSON.stringify(circles));
25 | console.log(JSON.stringify(circles2));
26 | }
27 | }
28 | }
29 |
30 | function encloses(a, b) {
31 | var dr = a.r - b.r + 1e-6, dx = b.x - a.x, dy = b.y - a.y;
32 | return dr > 0 && dr * dr > dx * dx + dy * dy;
33 | }
34 |
35 | function equals(a, b) {
36 | return Math.abs(a.r - b.r) < 1e-6
37 | && Math.abs(a.x - b.x) < 1e-6
38 | && Math.abs(a.y - b.y) < 1e-6;
39 | }
40 |
--------------------------------------------------------------------------------
/test/hierarchy/each-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3 = require("../../");
3 |
4 | var tree = {id: "root", children: [{id: "a", children: [{id: "ab"}]}, {id: "b", children: [{id: "ba"}]}]};
5 |
6 | tape("node.each() traverses a hierarchy in breadth-first order", function(test) {
7 | var root = d3.hierarchy(tree);
8 |
9 | var a = [];
10 | root.each(function(d) { a.push(d.data.id); });
11 | test.deepEqual(a, [ 'root', 'a', 'b', 'ab', 'ba' ]);
12 | test.end();
13 | });
14 |
15 | tape("node.eachBefore() traverses a hierarchy in pre-order traversal", function(test) {
16 | var root = d3.hierarchy(tree);
17 |
18 | var a = [];
19 | root.eachBefore(function(d) { a.push(d.data.id); });
20 | test.deepEqual(a, [ 'root', 'a', 'ab', 'b', 'ba' ]);
21 | test.end();
22 | });
23 |
24 | tape("node.eachAfter() traverses a hierarchy in post-order traversal", function(test) {
25 | var root = d3.hierarchy(tree);
26 |
27 | var a = [];
28 | root.eachAfter(function(d) { a.push(d.data.id); });
29 | test.deepEqual(a, [ 'ab', 'a', 'ba', 'b', 'root' ]);
30 | test.end();
31 | });
32 |
33 | tape("a hierarchy is an iterable equivalent to *node*.each()", function(test) {
34 | var root = d3.hierarchy(tree);
35 |
36 | var a = [];
37 | for (var d of root) a.push(d.data.id);
38 | test.deepEqual(a, [ 'root', 'a', 'b', 'ab', 'ba' ]);
39 | test.end();
40 | });
41 |
42 |
--------------------------------------------------------------------------------
/src/treemap/binary.js:
--------------------------------------------------------------------------------
1 | export default function(parent, x0, y0, x1, y1) {
2 | var nodes = parent.children,
3 | i, n = nodes.length,
4 | sum, sums = new Array(n + 1);
5 |
6 | for (sums[0] = sum = i = 0; i < n; ++i) {
7 | sums[i + 1] = sum += nodes[i].value;
8 | }
9 |
10 | partition(0, n, parent.value, x0, y0, x1, y1);
11 |
12 | function partition(i, j, value, x0, y0, x1, y1) {
13 | if (i >= j - 1) {
14 | var node = nodes[i];
15 | node.x0 = x0, node.y0 = y0;
16 | node.x1 = x1, node.y1 = y1;
17 | return;
18 | }
19 |
20 | var valueOffset = sums[i],
21 | valueTarget = (value / 2) + valueOffset,
22 | k = i + 1,
23 | hi = j - 1;
24 |
25 | while (k < hi) {
26 | var mid = k + hi >>> 1;
27 | if (sums[mid] < valueTarget) k = mid + 1;
28 | else hi = mid;
29 | }
30 |
31 | if ((valueTarget - sums[k - 1]) < (sums[k] - valueTarget) && i + 1 < k) --k;
32 |
33 | var valueLeft = sums[k] - valueOffset,
34 | valueRight = value - valueLeft;
35 |
36 | if ((x1 - x0) > (y1 - y0)) {
37 | var xk = value ? (x0 * valueRight + x1 * valueLeft) / value : x1;
38 | partition(i, k, valueLeft, x0, y0, xk, y1);
39 | partition(k, j, valueRight, xk, y0, x1, y1);
40 | } else {
41 | var yk = value ? (y0 * valueRight + y1 * valueLeft) / value : y1;
42 | partition(i, k, valueLeft, x0, y0, x1, yk);
43 | partition(k, j, valueRight, x0, yk, x1, y1);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/test/treemap/binary-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../"),
3 | round = require("./round");
4 |
5 | tape("treemapBinary(parent, x0, y0, x1, y1) generates a binary treemap layout", function(test) {
6 | var tile = d3_hierarchy.treemapBinary,
7 | root = {
8 | value: 24,
9 | children: [
10 | {value: 6},
11 | {value: 6},
12 | {value: 4},
13 | {value: 3},
14 | {value: 2},
15 | {value: 2},
16 | {value: 1}
17 | ]
18 | };
19 | tile(root, 0, 0, 6, 4);
20 | test.deepEqual(root.children.map(round), [
21 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
22 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
23 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
24 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
25 | {x0: 3.00, x1: 4.20, y0: 2.33, y1: 4.00},
26 | {x0: 4.20, x1: 5.40, y0: 2.33, y1: 4.00},
27 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
28 | ]);
29 | test.end();
30 | });
31 |
32 | tape("treemapBinary does not break on 0-sized inputs", function(test) {
33 | const data = ({children: [{value: 0}, {value: 0}, {value: 1}]});
34 | const root = d3_hierarchy.hierarchy(data).sum(d => d.value);
35 | const treemap = d3_hierarchy.treemap().tile(d3_hierarchy.treemapBinary);
36 | treemap(root);
37 | const a = root.leaves().map(d => [d.x0, d.x1, d.y0, d.y1]);
38 | test.deepEqual(a, [[0, 1, 0, 0], [1, 1, 0, 0], [0, 1, 0, 1]]);
39 | test.end();
40 | });
41 |
--------------------------------------------------------------------------------
/src/partition.js:
--------------------------------------------------------------------------------
1 | import roundNode from "./treemap/round.js";
2 | import treemapDice from "./treemap/dice.js";
3 |
4 | export default function() {
5 | var dx = 1,
6 | dy = 1,
7 | padding = 0,
8 | round = false;
9 |
10 | function partition(root) {
11 | var n = root.height + 1;
12 | root.x0 =
13 | root.y0 = padding;
14 | root.x1 = dx;
15 | root.y1 = dy / n;
16 | root.eachBefore(positionNode(dy, n));
17 | if (round) root.eachBefore(roundNode);
18 | return root;
19 | }
20 |
21 | function positionNode(dy, n) {
22 | return function(node) {
23 | if (node.children) {
24 | treemapDice(node, node.x0, dy * (node.depth + 1) / n, node.x1, dy * (node.depth + 2) / n);
25 | }
26 | var x0 = node.x0,
27 | y0 = node.y0,
28 | x1 = node.x1 - padding,
29 | y1 = node.y1 - padding;
30 | if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
31 | if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
32 | node.x0 = x0;
33 | node.y0 = y0;
34 | node.x1 = x1;
35 | node.y1 = y1;
36 | };
37 | }
38 |
39 | partition.round = function(x) {
40 | return arguments.length ? (round = !!x, partition) : round;
41 | };
42 |
43 | partition.size = function(x) {
44 | return arguments.length ? (dx = +x[0], dy = +x[1], partition) : [dx, dy];
45 | };
46 |
47 | partition.padding = function(x) {
48 | return arguments.length ? (padding = +x, partition) : padding;
49 | };
50 |
51 | return partition;
52 | }
53 |
--------------------------------------------------------------------------------
/test/treemap/dice-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../"),
3 | round = require("./round");
4 |
5 | tape("treemapDice(parent, x0, y0, x1, y1) generates a diced layout", function(test) {
6 | var tile = d3_hierarchy.treemapDice,
7 | root = {
8 | value: 24,
9 | children: [
10 | {value: 6},
11 | {value: 6},
12 | {value: 4},
13 | {value: 3},
14 | {value: 2},
15 | {value: 2},
16 | {value: 1}
17 | ]
18 | };
19 | tile(root, 0, 0, 4, 6);
20 | test.deepEqual(root.children.map(round), [
21 | {x0: 0.00, x1: 1.00, y0: 0.00, y1: 6.00},
22 | {x0: 1.00, x1: 2.00, y0: 0.00, y1: 6.00},
23 | {x0: 2.00, x1: 2.67, y0: 0.00, y1: 6.00},
24 | {x0: 2.67, x1: 3.17, y0: 0.00, y1: 6.00},
25 | {x0: 3.17, x1: 3.50, y0: 0.00, y1: 6.00},
26 | {x0: 3.50, x1: 3.83, y0: 0.00, y1: 6.00},
27 | {x0: 3.83, x1: 4.00, y0: 0.00, y1: 6.00}
28 | ]);
29 | test.end();
30 | });
31 |
32 | tape("treemapDice(parent, x0, y0, x1, y1) handles a degenerate empty parent", function(test) {
33 | var tile = d3_hierarchy.treemapDice,
34 | root = {
35 | value: 0,
36 | children: [
37 | {value: 0},
38 | {value: 0}
39 | ]
40 | };
41 | tile(root, 0, 0, 0, 4);
42 | test.deepEqual(root.children.map(round), [
43 | {x0: 0.00, x1: 0.00, y0: 0.00, y1: 4.00},
44 | {x0: 0.00, x1: 0.00, y0: 0.00, y1: 4.00}
45 | ]);
46 | test.end();
47 | });
48 |
--------------------------------------------------------------------------------
/test/treemap/slice-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../"),
3 | round = require("./round");
4 |
5 | tape("treemapSlice(parent, x0, y0, x1, y1) generates a sliced layout", function(test) {
6 | var tile = d3_hierarchy.treemapSlice,
7 | root = {
8 | value: 24,
9 | children: [
10 | {value: 6},
11 | {value: 6},
12 | {value: 4},
13 | {value: 3},
14 | {value: 2},
15 | {value: 2},
16 | {value: 1}
17 | ]
18 | };
19 | tile(root, 0, 0, 6, 4);
20 | test.deepEqual(root.children.map(round), [
21 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 1.00},
22 | {x0: 0.00, x1: 6.00, y0: 1.00, y1: 2.00},
23 | {x0: 0.00, x1: 6.00, y0: 2.00, y1: 2.67},
24 | {x0: 0.00, x1: 6.00, y0: 2.67, y1: 3.17},
25 | {x0: 0.00, x1: 6.00, y0: 3.17, y1: 3.50},
26 | {x0: 0.00, x1: 6.00, y0: 3.50, y1: 3.83},
27 | {x0: 0.00, x1: 6.00, y0: 3.83, y1: 4.00}
28 | ]);
29 | test.end();
30 | });
31 |
32 | tape("treemapSlice(parent, x0, y0, x1, y1) handles a degenerate empty parent", function(test) {
33 | var tile = d3_hierarchy.treemapSlice,
34 | root = {
35 | value: 0,
36 | children: [
37 | {value: 0},
38 | {value: 0}
39 | ]
40 | };
41 | tile(root, 0, 0, 4, 0);
42 | test.deepEqual(root.children.map(round), [
43 | {x0: 0.00, x1: 4.00, y0: 0.00, y1: 0.00},
44 | {x0: 0.00, x1: 4.00, y0: 0.00, y1: 0.00}
45 | ]);
46 | test.end();
47 | });
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2010-2016 Mike Bostock
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of the author nor the names of contributors may be used to
15 | endorse or promote products derived from this software without specific prior
16 | written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/test/pack/flare-test.js:
--------------------------------------------------------------------------------
1 | var fs = require("fs"),
2 | tape = require("tape"),
3 | d3_dsv = require("d3-dsv"),
4 | d3_hierarchy = require("../../");
5 |
6 | tape("pack(flare) produces the expected result", test(
7 | "test/data/flare.csv",
8 | "test/data/flare-pack.json"
9 | ));
10 |
11 | function test(inputFile, expectedFile) {
12 | return function(test) {
13 | const inputText = fs.readFileSync(inputFile, "utf8"),
14 | expectedText = fs.readFileSync(expectedFile, "utf8");
15 |
16 | var stratify = d3_hierarchy.stratify()
17 | .parentId(function(d) { var i = d.id.lastIndexOf("."); return i >= 0 ? d.id.slice(0, i) : null; });
18 |
19 | var pack = d3_hierarchy.pack()
20 | .size([960, 960]);
21 |
22 | var data = d3_dsv.csvParse(inputText),
23 | expected = JSON.parse(expectedText),
24 | actual = pack(stratify(data)
25 | .sum(function(d) { return d.value; })
26 | .sort(function(a, b) { return b.value - a.value || a.data.id.localeCompare(b.data.id); }));
27 |
28 | (function visit(node) {
29 | node.name = node.data.id.slice(node.data.id.lastIndexOf(".") + 1);
30 | node.x = round(node.x);
31 | node.y = round(node.y);
32 | node.r = round(node.r);
33 | delete node.id;
34 | delete node.parent;
35 | delete node.data;
36 | delete node.depth;
37 | delete node.height;
38 | if (node.children) node.children.forEach(visit);
39 | })(actual);
40 |
41 | (function visit(node) {
42 | node.x = round(node.x);
43 | node.y = round(node.y);
44 | node.r = round(node.r);
45 | if (node.children) node.children.forEach(visit);
46 | })(expected);
47 |
48 | test.deepEqual(actual, expected);
49 | test.end();
50 | }
51 | }
52 |
53 | function round(x) {
54 | return Math.round(x * 100) / 100;
55 | }
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "d3-hierarchy",
3 | "version": "2.0.0",
4 | "description": "Layout algorithms for visualizing hierarchical data.",
5 | "keywords": [
6 | "d3",
7 | "d3-module",
8 | "layout",
9 | "tree",
10 | "treemap",
11 | "hierarchy",
12 | "infovis"
13 | ],
14 | "homepage": "https://d3js.org/d3-hierarchy/",
15 | "license": "BSD-3-Clause",
16 | "author": {
17 | "name": "Mike Bostock",
18 | "url": "http://bost.ocks.org/mike"
19 | },
20 | "main": "dist/d3-hierarchy.js",
21 | "unpkg": "dist/d3-hierarchy.min.js",
22 | "jsdelivr": "dist/d3-hierarchy.min.js",
23 | "module": "src/index.js",
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/d3/d3-hierarchy.git"
27 | },
28 | "files": [
29 | "dist/**/*.js",
30 | "src/**/*.js"
31 | ],
32 | "scripts": {
33 | "pretest": "rollup -c",
34 | "test": "tape 'test/**/*-test.js' && eslint src test",
35 | "prepublishOnly": "rm -rf dist && yarn test",
36 | "postpublish": "git push && git push --tags && cd ../d3.github.com && git pull && cp ../${npm_package_name}/dist/${npm_package_name}.js ${npm_package_name}.v${npm_package_version%%.*}.js && cp ../${npm_package_name}/dist/${npm_package_name}.min.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git add ${npm_package_name}.v${npm_package_version%%.*}.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git commit -m \"${npm_package_name} ${npm_package_version}\" && git push && cd - && zip -j dist/${npm_package_name}.zip -- LICENSE README.md dist/${npm_package_name}.js dist/${npm_package_name}.min.js"
37 | },
38 | "sideEffects": false,
39 | "devDependencies": {
40 | "benchmark": "^2.1.4",
41 | "d3-array": "1.2.0 - 2",
42 | "d3-dsv": "1 - 2",
43 | "d3-random": "1.1.0 - 2",
44 | "eslint": "6",
45 | "rollup": "1",
46 | "rollup-plugin-terser": "5",
47 | "tape": "4"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/pack/find-place-bugs.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | // Look for numerical inconsistencies between the place() and intersects()
4 | // methods from pack/siblings.js
5 |
6 | // The place and intersect functions are not exported, so we duplicate them here
7 | function place(a, b, c) {
8 | var dx = b.x - a.x, x, a2,
9 | dy = b.y - a.y, y, b2,
10 | d2 = dx * dx + dy * dy;
11 | if (d2) {
12 | a2 = a.r + c.r, a2 *= a2;
13 | b2 = b.r + c.r, b2 *= b2;
14 | if (a2 > b2) {
15 | x = (d2 + b2 - a2) / (2 * d2);
16 | y = Math.sqrt(Math.max(0, b2 / d2 - x * x));
17 | c.x = b.x - x * dx - y * dy;
18 | c.y = b.y - x * dy + y * dx;
19 | } else {
20 | x = (d2 + a2 - b2) / (2 * d2);
21 | y = Math.sqrt(Math.max(0, a2 / d2 - x * x));
22 | c.x = a.x + x * dx - y * dy;
23 | c.y = a.y + x * dy + y * dx;
24 | }
25 | } else {
26 | c.x = a.x + c.r;
27 | c.y = a.y;
28 | }
29 |
30 | // This last part is not part of the original function!
31 | if (intersects(a, c) || intersects(b, c)) {
32 | console.log(`a = {x: ${a.x}, y: ${a.y}, r: ${a.r}},`);
33 | console.log(`b = {x: ${b.x}, y: ${b.y}, r: ${b.r}},`);
34 | console.log(`c = {r: ${c.r}}`);
35 | console.log();
36 | }
37 | }
38 |
39 | function intersects(a, b) {
40 | var dr = a.r + b.r - 1e-6, dx = b.x - a.x, dy = b.y - a.y;
41 | return dr > 0 && dr * dr > dx * dx + dy * dy;
42 | }
43 |
44 | // Create n random circles.
45 | // The first two are placed touching on the x-axis; the rest are unplaced
46 | function randomCircles(n) {
47 | const r = [];
48 | for (var i = 0; i < n; i++) {
49 | r.push({ r: Math.random() * (1 << (Math.random() * 30)) });
50 | }
51 | r[0].x = -r[1].r, r[1].x = r[0].r, r[0].y = r[1].y = 0;
52 | return r;
53 | }
54 |
55 | function test() {
56 | for(;;) {
57 | const [a,b,c,d] = randomCircles(4);
58 | place(b, a, c);
59 | place(a, c, d);
60 | }
61 | }
62 |
63 | test();
64 |
--------------------------------------------------------------------------------
/test/treemap/sliceDice-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../"),
3 | round = require("./round");
4 |
5 | tape("treemapSliceDice(parent, x0, y0, x1, y1) uses slice for odd depth", function(test) {
6 | var tile = d3_hierarchy.treemapSliceDice,
7 | root = {
8 | depth: 1,
9 | value: 24,
10 | children: [
11 | {value: 6},
12 | {value: 6},
13 | {value: 4},
14 | {value: 3},
15 | {value: 2},
16 | {value: 2},
17 | {value: 1}
18 | ]
19 | };
20 | tile(root, 0, 0, 6, 4);
21 | test.deepEqual(root.children.map(round), [
22 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 1.00},
23 | {x0: 0.00, x1: 6.00, y0: 1.00, y1: 2.00},
24 | {x0: 0.00, x1: 6.00, y0: 2.00, y1: 2.67},
25 | {x0: 0.00, x1: 6.00, y0: 2.67, y1: 3.17},
26 | {x0: 0.00, x1: 6.00, y0: 3.17, y1: 3.50},
27 | {x0: 0.00, x1: 6.00, y0: 3.50, y1: 3.83},
28 | {x0: 0.00, x1: 6.00, y0: 3.83, y1: 4.00}
29 | ]);
30 | test.end();
31 | });
32 |
33 | tape("treemapSliceDice(parent, x0, y0, x1, y1) uses dice for even depth", function(test) {
34 | var tile = d3_hierarchy.treemapSliceDice,
35 | root = {
36 | depth: 2,
37 | value: 24,
38 | children: [
39 | {value: 6},
40 | {value: 6},
41 | {value: 4},
42 | {value: 3},
43 | {value: 2},
44 | {value: 2},
45 | {value: 1}
46 | ]
47 | };
48 | tile(root, 0, 0, 4, 6);
49 | test.deepEqual(root.children.map(round), [
50 | {x0: 0.00, x1: 1.00, y0: 0.00, y1: 6.00},
51 | {x0: 1.00, x1: 2.00, y0: 0.00, y1: 6.00},
52 | {x0: 2.00, x1: 2.67, y0: 0.00, y1: 6.00},
53 | {x0: 2.67, x1: 3.17, y0: 0.00, y1: 6.00},
54 | {x0: 3.17, x1: 3.50, y0: 0.00, y1: 6.00},
55 | {x0: 3.50, x1: 3.83, y0: 0.00, y1: 6.00},
56 | {x0: 3.83, x1: 4.00, y0: 0.00, y1: 6.00}
57 | ]);
58 | test.end();
59 | });
60 |
--------------------------------------------------------------------------------
/src/treemap/squarify.js:
--------------------------------------------------------------------------------
1 | import treemapDice from "./dice.js";
2 | import treemapSlice from "./slice.js";
3 |
4 | export var phi = (1 + Math.sqrt(5)) / 2;
5 |
6 | export function squarifyRatio(ratio, parent, x0, y0, x1, y1) {
7 | var rows = [],
8 | nodes = parent.children,
9 | row,
10 | nodeValue,
11 | i0 = 0,
12 | i1 = 0,
13 | n = nodes.length,
14 | dx, dy,
15 | value = parent.value,
16 | sumValue,
17 | minValue,
18 | maxValue,
19 | newRatio,
20 | minRatio,
21 | alpha,
22 | beta;
23 |
24 | while (i0 < n) {
25 | dx = x1 - x0, dy = y1 - y0;
26 |
27 | // Find the next non-empty node.
28 | do sumValue = nodes[i1++].value; while (!sumValue && i1 < n);
29 | minValue = maxValue = sumValue;
30 | alpha = Math.max(dy / dx, dx / dy) / (value * ratio);
31 | beta = sumValue * sumValue * alpha;
32 | minRatio = Math.max(maxValue / beta, beta / minValue);
33 |
34 | // Keep adding nodes while the aspect ratio maintains or improves.
35 | for (; i1 < n; ++i1) {
36 | sumValue += nodeValue = nodes[i1].value;
37 | if (nodeValue < minValue) minValue = nodeValue;
38 | if (nodeValue > maxValue) maxValue = nodeValue;
39 | beta = sumValue * sumValue * alpha;
40 | newRatio = Math.max(maxValue / beta, beta / minValue);
41 | if (newRatio > minRatio) { sumValue -= nodeValue; break; }
42 | minRatio = newRatio;
43 | }
44 |
45 | // Position and record the row orientation.
46 | rows.push(row = {value: sumValue, dice: dx < dy, children: nodes.slice(i0, i1)});
47 | if (row.dice) treemapDice(row, x0, y0, x1, value ? y0 += dy * sumValue / value : y1);
48 | else treemapSlice(row, x0, y0, value ? x0 += dx * sumValue / value : x1, y1);
49 | value -= sumValue, i0 = i1;
50 | }
51 |
52 | return rows;
53 | }
54 |
55 | export default (function custom(ratio) {
56 |
57 | function squarify(parent, x0, y0, x1, y1) {
58 | squarifyRatio(ratio, parent, x0, y0, x1, y1);
59 | }
60 |
61 | squarify.ratio = function(x) {
62 | return custom((x = +x) > 1 ? x : 1);
63 | };
64 |
65 | return squarify;
66 | })(phi);
67 |
--------------------------------------------------------------------------------
/src/stratify.js:
--------------------------------------------------------------------------------
1 | import {required} from "./accessors.js";
2 | import {Node, computeHeight} from "./hierarchy/index.js";
3 |
4 | var preroot = {depth: -1},
5 | ambiguous = {};
6 |
7 | function defaultId(d) {
8 | return d.id;
9 | }
10 |
11 | function defaultParentId(d) {
12 | return d.parentId;
13 | }
14 |
15 | export default function() {
16 | var id = defaultId,
17 | parentId = defaultParentId;
18 |
19 | function stratify(data) {
20 | var nodes = Array.from(data),
21 | n = nodes.length,
22 | d,
23 | i,
24 | root,
25 | parent,
26 | node,
27 | nodeId,
28 | nodeKey,
29 | nodeByKey = new Map;
30 |
31 | for (i = 0; i < n; ++i) {
32 | d = nodes[i], node = nodes[i] = new Node(d);
33 | if ((nodeId = id(d, i, data)) != null && (nodeId += "")) {
34 | nodeKey = node.id = nodeId;
35 | nodeByKey.set(nodeKey, nodeByKey.has(nodeKey) ? ambiguous : node);
36 | }
37 | if ((nodeId = parentId(d, i, data)) != null && (nodeId += "")) {
38 | node.parent = nodeId;
39 | }
40 | }
41 |
42 | for (i = 0; i < n; ++i) {
43 | node = nodes[i];
44 | if (nodeId = node.parent) {
45 | parent = nodeByKey.get(nodeId);
46 | if (!parent) throw new Error("missing: " + nodeId);
47 | if (parent === ambiguous) throw new Error("ambiguous: " + nodeId);
48 | if (parent.children) parent.children.push(node);
49 | else parent.children = [node];
50 | node.parent = parent;
51 | } else {
52 | if (root) throw new Error("multiple roots");
53 | root = node;
54 | }
55 | }
56 |
57 | if (!root) throw new Error("no root");
58 | root.parent = preroot;
59 | root.eachBefore(function(node) { node.depth = node.parent.depth + 1; --n; }).eachBefore(computeHeight);
60 | root.parent = null;
61 | if (n > 0) throw new Error("cycle");
62 |
63 | return root;
64 | }
65 |
66 | stratify.id = function(x) {
67 | return arguments.length ? (id = required(x), stratify) : id;
68 | };
69 |
70 | stratify.parentId = function(x) {
71 | return arguments.length ? (parentId = required(x), stratify) : parentId;
72 | };
73 |
74 | return stratify;
75 | }
76 |
--------------------------------------------------------------------------------
/src/pack/index.js:
--------------------------------------------------------------------------------
1 | import {packEnclose} from "./siblings.js";
2 | import {optional} from "../accessors.js";
3 | import constant, {constantZero} from "../constant.js";
4 |
5 | function defaultRadius(d) {
6 | return Math.sqrt(d.value);
7 | }
8 |
9 | export default function() {
10 | var radius = null,
11 | dx = 1,
12 | dy = 1,
13 | padding = constantZero;
14 |
15 | function pack(root) {
16 | root.x = dx / 2, root.y = dy / 2;
17 | if (radius) {
18 | root.eachBefore(radiusLeaf(radius))
19 | .eachAfter(packChildren(padding, 0.5))
20 | .eachBefore(translateChild(1));
21 | } else {
22 | root.eachBefore(radiusLeaf(defaultRadius))
23 | .eachAfter(packChildren(constantZero, 1))
24 | .eachAfter(packChildren(padding, root.r / Math.min(dx, dy)))
25 | .eachBefore(translateChild(Math.min(dx, dy) / (2 * root.r)));
26 | }
27 | return root;
28 | }
29 |
30 | pack.radius = function(x) {
31 | return arguments.length ? (radius = optional(x), pack) : radius;
32 | };
33 |
34 | pack.size = function(x) {
35 | return arguments.length ? (dx = +x[0], dy = +x[1], pack) : [dx, dy];
36 | };
37 |
38 | pack.padding = function(x) {
39 | return arguments.length ? (padding = typeof x === "function" ? x : constant(+x), pack) : padding;
40 | };
41 |
42 | return pack;
43 | }
44 |
45 | function radiusLeaf(radius) {
46 | return function(node) {
47 | if (!node.children) {
48 | node.r = Math.max(0, +radius(node) || 0);
49 | }
50 | };
51 | }
52 |
53 | function packChildren(padding, k) {
54 | return function(node) {
55 | if (children = node.children) {
56 | var children,
57 | i,
58 | n = children.length,
59 | r = padding(node) * k || 0,
60 | e;
61 |
62 | if (r) for (i = 0; i < n; ++i) children[i].r += r;
63 | e = packEnclose(children);
64 | if (r) for (i = 0; i < n; ++i) children[i].r -= r;
65 | node.r = e + r;
66 | }
67 | };
68 | }
69 |
70 | function translateChild(k) {
71 | return function(node) {
72 | var parent = node.parent;
73 | node.r *= k;
74 | if (parent) {
75 | node.x = parent.x + k * node.x;
76 | node.y = parent.y + k * node.y;
77 | }
78 | };
79 | }
80 |
--------------------------------------------------------------------------------
/src/cluster.js:
--------------------------------------------------------------------------------
1 | function defaultSeparation(a, b) {
2 | return a.parent === b.parent ? 1 : 2;
3 | }
4 |
5 | function meanX(children) {
6 | return children.reduce(meanXReduce, 0) / children.length;
7 | }
8 |
9 | function meanXReduce(x, c) {
10 | return x + c.x;
11 | }
12 |
13 | function maxY(children) {
14 | return 1 + children.reduce(maxYReduce, 0);
15 | }
16 |
17 | function maxYReduce(y, c) {
18 | return Math.max(y, c.y);
19 | }
20 |
21 | function leafLeft(node) {
22 | var children;
23 | while (children = node.children) node = children[0];
24 | return node;
25 | }
26 |
27 | function leafRight(node) {
28 | var children;
29 | while (children = node.children) node = children[children.length - 1];
30 | return node;
31 | }
32 |
33 | export default function() {
34 | var separation = defaultSeparation,
35 | dx = 1,
36 | dy = 1,
37 | nodeSize = false;
38 |
39 | function cluster(root) {
40 | var previousNode,
41 | x = 0;
42 |
43 | // First walk, computing the initial x & y values.
44 | root.eachAfter(function(node) {
45 | var children = node.children;
46 | if (children) {
47 | node.x = meanX(children);
48 | node.y = maxY(children);
49 | } else {
50 | node.x = previousNode ? x += separation(node, previousNode) : 0;
51 | node.y = 0;
52 | previousNode = node;
53 | }
54 | });
55 |
56 | var left = leafLeft(root),
57 | right = leafRight(root),
58 | x0 = left.x - separation(left, right) / 2,
59 | x1 = right.x + separation(right, left) / 2;
60 |
61 | // Second walk, normalizing x & y to the desired size.
62 | return root.eachAfter(nodeSize ? function(node) {
63 | node.x = (node.x - root.x) * dx;
64 | node.y = (root.y - node.y) * dy;
65 | } : function(node) {
66 | node.x = (node.x - x0) / (x1 - x0) * dx;
67 | node.y = (1 - (root.y ? node.y / root.y : 1)) * dy;
68 | });
69 | }
70 |
71 | cluster.separation = function(x) {
72 | return arguments.length ? (separation = x, cluster) : separation;
73 | };
74 |
75 | cluster.size = function(x) {
76 | return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], cluster) : (nodeSize ? null : [dx, dy]);
77 | };
78 |
79 | cluster.nodeSize = function(x) {
80 | return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], cluster) : (nodeSize ? [dx, dy] : null);
81 | };
82 |
83 | return cluster;
84 | }
85 |
--------------------------------------------------------------------------------
/test/treemap/flare-test.js:
--------------------------------------------------------------------------------
1 | var fs = require("fs"),
2 | tape = require("tape"),
3 | d3_dsv = require("d3-dsv"),
4 | d3_hierarchy = require("../../");
5 |
6 | tape("treemap(flare) produces the expected result with a squarified ratio of φ", test(
7 | "test/data/flare.csv",
8 | "test/data/flare-phi.json",
9 | d3_hierarchy.treemapSquarify
10 | ));
11 |
12 | tape("treemap(flare) produces the expected result with a squarified ratio of 1", test(
13 | "test/data/flare.csv",
14 | "test/data/flare-one.json",
15 | d3_hierarchy.treemapSquarify.ratio(1)
16 | ));
17 |
18 | function test(inputFile, expectedFile, tile) {
19 | return function(test) {
20 | const inputText = fs.readFileSync(inputFile, "utf8"),
21 | expectedText = fs.readFileSync(expectedFile, "utf8");
22 |
23 | var stratify = d3_hierarchy.stratify()
24 | .parentId(function(d) { var i = d.id.lastIndexOf("."); return i >= 0 ? d.id.slice(0, i) : null; });
25 |
26 | var treemap = d3_hierarchy.treemap()
27 | .tile(tile)
28 | .size([960, 500]);
29 |
30 | var data = d3_dsv.csvParse(inputText),
31 | expected = JSON.parse(expectedText),
32 | actual = treemap(stratify(data)
33 | .sum(function(d) { return d.value; })
34 | .sort(function(a, b) { return b.value - a.value || a.data.id.localeCompare(b.data.id); }));
35 |
36 | (function visit(node) {
37 | node.name = node.data.id.slice(node.data.id.lastIndexOf(".") + 1);
38 | node.x0 = round(node.x0);
39 | node.y0 = round(node.y0);
40 | node.x1 = round(node.x1);
41 | node.y1 = round(node.y1);
42 | delete node.id;
43 | delete node.parent;
44 | delete node.data;
45 | delete node._squarify;
46 | delete node.height;
47 | if (node.children) node.children.forEach(visit);
48 | })(actual);
49 |
50 | (function visit(node) {
51 | node.x0 = round(node.x);
52 | node.y0 = round(node.y);
53 | node.x1 = round(node.x + node.dx);
54 | node.y1 = round(node.y + node.dy);
55 | delete node.x;
56 | delete node.y;
57 | delete node.dx;
58 | delete node.dy;
59 | if (node.children) {
60 | node.children.reverse(); // D3 3.x bug
61 | node.children.forEach(visit);
62 | }
63 | })(expected);
64 |
65 | test.deepEqual(actual, expected);
66 | test.end();
67 | }
68 | }
69 |
70 | function round(x) {
71 | return Math.round(x * 100) / 100;
72 | }
73 |
--------------------------------------------------------------------------------
/src/hierarchy/index.js:
--------------------------------------------------------------------------------
1 | import node_count from "./count.js";
2 | import node_each from "./each.js";
3 | import node_eachBefore from "./eachBefore.js";
4 | import node_eachAfter from "./eachAfter.js";
5 | import node_find from "./find.js";
6 | import node_sum from "./sum.js";
7 | import node_sort from "./sort.js";
8 | import node_path from "./path.js";
9 | import node_ancestors from "./ancestors.js";
10 | import node_descendants from "./descendants.js";
11 | import node_leaves from "./leaves.js";
12 | import node_links from "./links.js";
13 | import node_iterator from "./iterator.js";
14 |
15 | export default function hierarchy(data, children) {
16 | if (data instanceof Map) {
17 | data = [undefined, data];
18 | if (children === undefined) children = mapChildren;
19 | } else if (children === undefined) {
20 | children = objectChildren;
21 | }
22 |
23 | var root = new Node(data),
24 | node,
25 | nodes = [root],
26 | child,
27 | childs,
28 | i,
29 | n;
30 |
31 | while (node = nodes.pop()) {
32 | if ((childs = children(node.data)) && (n = (childs = Array.from(childs)).length)) {
33 | node.children = childs;
34 | for (i = n - 1; i >= 0; --i) {
35 | nodes.push(child = childs[i] = new Node(childs[i]));
36 | child.parent = node;
37 | child.depth = node.depth + 1;
38 | }
39 | }
40 | }
41 |
42 | return root.eachBefore(computeHeight);
43 | }
44 |
45 | function node_copy() {
46 | return hierarchy(this).eachBefore(copyData);
47 | }
48 |
49 | function objectChildren(d) {
50 | return d.children;
51 | }
52 |
53 | function mapChildren(d) {
54 | return Array.isArray(d) ? d[1] : null;
55 | }
56 |
57 | function copyData(node) {
58 | if (node.data.value !== undefined) node.value = node.data.value;
59 | node.data = node.data.data;
60 | }
61 |
62 | export function computeHeight(node) {
63 | var height = 0;
64 | do node.height = height;
65 | while ((node = node.parent) && (node.height < ++height));
66 | }
67 |
68 | export function Node(data) {
69 | this.data = data;
70 | this.depth =
71 | this.height = 0;
72 | this.parent = null;
73 | }
74 |
75 | Node.prototype = hierarchy.prototype = {
76 | constructor: Node,
77 | count: node_count,
78 | each: node_each,
79 | eachAfter: node_eachAfter,
80 | eachBefore: node_eachBefore,
81 | find: node_find,
82 | sum: node_sum,
83 | sort: node_sort,
84 | path: node_path,
85 | ancestors: node_ancestors,
86 | descendants: node_descendants,
87 | leaves: node_leaves,
88 | links: node_links,
89 | copy: node_copy,
90 | [Symbol.iterator]: node_iterator
91 | };
92 |
--------------------------------------------------------------------------------
/src/treemap/index.js:
--------------------------------------------------------------------------------
1 | import roundNode from "./round.js";
2 | import squarify from "./squarify.js";
3 | import {required} from "../accessors.js";
4 | import constant, {constantZero} from "../constant.js";
5 |
6 | export default function() {
7 | var tile = squarify,
8 | round = false,
9 | dx = 1,
10 | dy = 1,
11 | paddingStack = [0],
12 | paddingInner = constantZero,
13 | paddingTop = constantZero,
14 | paddingRight = constantZero,
15 | paddingBottom = constantZero,
16 | paddingLeft = constantZero;
17 |
18 | function treemap(root) {
19 | root.x0 =
20 | root.y0 = 0;
21 | root.x1 = dx;
22 | root.y1 = dy;
23 | root.eachBefore(positionNode);
24 | paddingStack = [0];
25 | if (round) root.eachBefore(roundNode);
26 | return root;
27 | }
28 |
29 | function positionNode(node) {
30 | var p = paddingStack[node.depth],
31 | x0 = node.x0 + p,
32 | y0 = node.y0 + p,
33 | x1 = node.x1 - p,
34 | y1 = node.y1 - p;
35 | if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
36 | if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
37 | node.x0 = x0;
38 | node.y0 = y0;
39 | node.x1 = x1;
40 | node.y1 = y1;
41 | if (node.children) {
42 | p = paddingStack[node.depth + 1] = paddingInner(node) / 2;
43 | x0 += paddingLeft(node) - p;
44 | y0 += paddingTop(node) - p;
45 | x1 -= paddingRight(node) - p;
46 | y1 -= paddingBottom(node) - p;
47 | if (x1 < x0) x0 = x1 = (x0 + x1) / 2;
48 | if (y1 < y0) y0 = y1 = (y0 + y1) / 2;
49 | tile(node, x0, y0, x1, y1);
50 | }
51 | }
52 |
53 | treemap.round = function(x) {
54 | return arguments.length ? (round = !!x, treemap) : round;
55 | };
56 |
57 | treemap.size = function(x) {
58 | return arguments.length ? (dx = +x[0], dy = +x[1], treemap) : [dx, dy];
59 | };
60 |
61 | treemap.tile = function(x) {
62 | return arguments.length ? (tile = required(x), treemap) : tile;
63 | };
64 |
65 | treemap.padding = function(x) {
66 | return arguments.length ? treemap.paddingInner(x).paddingOuter(x) : treemap.paddingInner();
67 | };
68 |
69 | treemap.paddingInner = function(x) {
70 | return arguments.length ? (paddingInner = typeof x === "function" ? x : constant(+x), treemap) : paddingInner;
71 | };
72 |
73 | treemap.paddingOuter = function(x) {
74 | return arguments.length ? treemap.paddingTop(x).paddingRight(x).paddingBottom(x).paddingLeft(x) : treemap.paddingTop();
75 | };
76 |
77 | treemap.paddingTop = function(x) {
78 | return arguments.length ? (paddingTop = typeof x === "function" ? x : constant(+x), treemap) : paddingTop;
79 | };
80 |
81 | treemap.paddingRight = function(x) {
82 | return arguments.length ? (paddingRight = typeof x === "function" ? x : constant(+x), treemap) : paddingRight;
83 | };
84 |
85 | treemap.paddingBottom = function(x) {
86 | return arguments.length ? (paddingBottom = typeof x === "function" ? x : constant(+x), treemap) : paddingBottom;
87 | };
88 |
89 | treemap.paddingLeft = function(x) {
90 | return arguments.length ? (paddingLeft = typeof x === "function" ? x : constant(+x), treemap) : paddingLeft;
91 | };
92 |
93 | return treemap;
94 | }
95 |
--------------------------------------------------------------------------------
/test/treemap/resquarify-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../"),
3 | round = require("./round");
4 |
5 | tape("treemapResquarify(parent, x0, y0, x1, y1) produces a stable update", function(test) {
6 | var tile = d3_hierarchy.treemapResquarify,
7 | root = {value: 20, children: [{value: 10}, {value: 10}]};
8 | tile(root, 0, 0, 20, 10);
9 | test.deepEqual(root.children.map(round), [
10 | {x0: 0, x1: 10, y0: 0, y1: 10},
11 | {x0: 10, x1: 20, y0: 0, y1: 10}
12 | ]);
13 | tile(root, 0, 0, 10, 20);
14 | test.deepEqual(root.children.map(round), [
15 | {x0: 0, x1: 5, y0: 0, y1: 20},
16 | {x0: 5, x1: 10, y0: 0, y1: 20}
17 | ]);
18 | test.end();
19 | });
20 |
21 | tape("treemapResquarify.ratio(ratio) observes the specified ratio", function(test) {
22 | var tile = d3_hierarchy.treemapResquarify.ratio(1),
23 | root = {
24 | value: 24,
25 | children: [
26 | {value: 6},
27 | {value: 6},
28 | {value: 4},
29 | {value: 3},
30 | {value: 2},
31 | {value: 2},
32 | {value: 1}
33 | ]
34 | };
35 | tile(root, 0, 0, 6, 4);
36 | test.deepEqual(root.children.map(round), [
37 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
38 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
39 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
40 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
41 | {x0: 3.00, x1: 4.20, y0: 2.33, y1: 4.00},
42 | {x0: 4.20, x1: 5.40, y0: 2.33, y1: 4.00},
43 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
44 | ]);
45 | test.end();
46 | });
47 |
48 | tape("treemapResquarify.ratio(ratio) is stable if the ratio is unchanged", function(test) {
49 | var root = {value: 20, children: [{value: 10}, {value: 10}]};
50 | d3_hierarchy.treemapResquarify(root, 0, 0, 20, 10);
51 | test.deepEqual(root.children.map(round), [
52 | {x0: 0, x1: 10, y0: 0, y1: 10},
53 | {x0: 10, x1: 20, y0: 0, y1: 10}
54 | ]);
55 | d3_hierarchy.treemapResquarify.ratio((1 + Math.sqrt(5)) / 2)(root, 0, 0, 10, 20);
56 | test.deepEqual(root.children.map(round), [
57 | {x0: 0, x1: 5, y0: 0, y1: 20},
58 | {x0: 5, x1: 10, y0: 0, y1: 20}
59 | ]);
60 | test.end();
61 | });
62 |
63 | tape("treemapResquarify.ratio(ratio) is unstable if the ratio is changed", function(test) {
64 | var root = {value: 20, children: [{value: 10}, {value: 10}]};
65 | d3_hierarchy.treemapResquarify(root, 0, 0, 20, 10);
66 | test.deepEqual(root.children.map(round), [
67 | {x0: 0, x1: 10, y0: 0, y1: 10},
68 | {x0: 10, x1: 20, y0: 0, y1: 10}
69 | ]);
70 | d3_hierarchy.treemapResquarify.ratio(1)(root, 0, 0, 10, 20);
71 | test.deepEqual(root.children.map(round), [
72 | {x0: 0, x1: 10, y0: 0, y1: 10},
73 | {x0: 0, x1: 10, y0: 10, y1: 20}
74 | ]);
75 | test.end();
76 | });
77 |
78 | tape("treemapResquarify does not break on 0-sized inputs", function(test) {
79 | var root = d3_hierarchy.hierarchy({children: [{children:[{value: 0}]}, {value: 1}]});
80 | const treemap = d3_hierarchy.treemap().tile(d3_hierarchy.treemapResquarify);
81 | treemap(root.sum(d => d.value));
82 | treemap(root.sum(d => d.sum));
83 | const a = root.leaves().map(d => [d.x0, d.x1, d.y0, d.y1]);
84 | test.deepEqual(a, [[0, 1, 0, 0], [0, 1, 0, 0]]);
85 | test.end();
86 | });
87 |
--------------------------------------------------------------------------------
/src/pack/enclose.js:
--------------------------------------------------------------------------------
1 | import {shuffle} from "../array.js";
2 |
3 | export default function(circles) {
4 | var i = 0, n = (circles = shuffle(Array.from(circles))).length, B = [], p, e;
5 |
6 | while (i < n) {
7 | p = circles[i];
8 | if (e && enclosesWeak(e, p)) ++i;
9 | else e = encloseBasis(B = extendBasis(B, p)), i = 0;
10 | }
11 |
12 | return e;
13 | }
14 |
15 | function extendBasis(B, p) {
16 | var i, j;
17 |
18 | if (enclosesWeakAll(p, B)) return [p];
19 |
20 | // If we get here then B must have at least one element.
21 | for (i = 0; i < B.length; ++i) {
22 | if (enclosesNot(p, B[i])
23 | && enclosesWeakAll(encloseBasis2(B[i], p), B)) {
24 | return [B[i], p];
25 | }
26 | }
27 |
28 | // If we get here then B must have at least two elements.
29 | for (i = 0; i < B.length - 1; ++i) {
30 | for (j = i + 1; j < B.length; ++j) {
31 | if (enclosesNot(encloseBasis2(B[i], B[j]), p)
32 | && enclosesNot(encloseBasis2(B[i], p), B[j])
33 | && enclosesNot(encloseBasis2(B[j], p), B[i])
34 | && enclosesWeakAll(encloseBasis3(B[i], B[j], p), B)) {
35 | return [B[i], B[j], p];
36 | }
37 | }
38 | }
39 |
40 | // If we get here then something is very wrong.
41 | throw new Error;
42 | }
43 |
44 | function enclosesNot(a, b) {
45 | var dr = a.r - b.r, dx = b.x - a.x, dy = b.y - a.y;
46 | return dr < 0 || dr * dr < dx * dx + dy * dy;
47 | }
48 |
49 | function enclosesWeak(a, b) {
50 | 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;
51 | return dr > 0 && dr * dr > dx * dx + dy * dy;
52 | }
53 |
54 | function enclosesWeakAll(a, B) {
55 | for (var i = 0; i < B.length; ++i) {
56 | if (!enclosesWeak(a, B[i])) {
57 | return false;
58 | }
59 | }
60 | return true;
61 | }
62 |
63 | function encloseBasis(B) {
64 | switch (B.length) {
65 | case 1: return encloseBasis1(B[0]);
66 | case 2: return encloseBasis2(B[0], B[1]);
67 | case 3: return encloseBasis3(B[0], B[1], B[2]);
68 | }
69 | }
70 |
71 | function encloseBasis1(a) {
72 | return {
73 | x: a.x,
74 | y: a.y,
75 | r: a.r
76 | };
77 | }
78 |
79 | function encloseBasis2(a, b) {
80 | var x1 = a.x, y1 = a.y, r1 = a.r,
81 | x2 = b.x, y2 = b.y, r2 = b.r,
82 | x21 = x2 - x1, y21 = y2 - y1, r21 = r2 - r1,
83 | l = Math.sqrt(x21 * x21 + y21 * y21);
84 | return {
85 | x: (x1 + x2 + x21 / l * r21) / 2,
86 | y: (y1 + y2 + y21 / l * r21) / 2,
87 | r: (l + r1 + r2) / 2
88 | };
89 | }
90 |
91 | function encloseBasis3(a, b, c) {
92 | var x1 = a.x, y1 = a.y, r1 = a.r,
93 | x2 = b.x, y2 = b.y, r2 = b.r,
94 | x3 = c.x, y3 = c.y, r3 = c.r,
95 | a2 = x1 - x2,
96 | a3 = x1 - x3,
97 | b2 = y1 - y2,
98 | b3 = y1 - y3,
99 | c2 = r2 - r1,
100 | c3 = r3 - r1,
101 | d1 = x1 * x1 + y1 * y1 - r1 * r1,
102 | d2 = d1 - x2 * x2 - y2 * y2 + r2 * r2,
103 | d3 = d1 - x3 * x3 - y3 * y3 + r3 * r3,
104 | ab = a3 * b2 - a2 * b3,
105 | xa = (b2 * d3 - b3 * d2) / (ab * 2) - x1,
106 | xb = (b3 * c2 - b2 * c3) / ab,
107 | ya = (a3 * d2 - a2 * d3) / (ab * 2) - y1,
108 | yb = (a2 * c3 - a3 * c2) / ab,
109 | A = xb * xb + yb * yb - 1,
110 | B = 2 * (r1 + xa * xb + ya * yb),
111 | C = xa * xa + ya * ya - r1 * r1,
112 | r = -(A ? (B + Math.sqrt(B * B - 4 * A * C)) / (2 * A) : C / B);
113 | return {
114 | x: x1 + xa + xb * r,
115 | y: y1 + ya + yb * r,
116 | r: r
117 | };
118 | }
119 |
--------------------------------------------------------------------------------
/src/pack/siblings.js:
--------------------------------------------------------------------------------
1 | import array from "../array.js";
2 | import enclose from "./enclose.js";
3 |
4 | function place(b, a, c) {
5 | var dx = b.x - a.x, x, a2,
6 | dy = b.y - a.y, y, b2,
7 | d2 = dx * dx + dy * dy;
8 | if (d2) {
9 | a2 = a.r + c.r, a2 *= a2;
10 | b2 = b.r + c.r, b2 *= b2;
11 | if (a2 > b2) {
12 | x = (d2 + b2 - a2) / (2 * d2);
13 | y = Math.sqrt(Math.max(0, b2 / d2 - x * x));
14 | c.x = b.x - x * dx - y * dy;
15 | c.y = b.y - x * dy + y * dx;
16 | } else {
17 | x = (d2 + a2 - b2) / (2 * d2);
18 | y = Math.sqrt(Math.max(0, a2 / d2 - x * x));
19 | c.x = a.x + x * dx - y * dy;
20 | c.y = a.y + x * dy + y * dx;
21 | }
22 | } else {
23 | c.x = a.x + c.r;
24 | c.y = a.y;
25 | }
26 | }
27 |
28 | function intersects(a, b) {
29 | var dr = a.r + b.r - 1e-6, dx = b.x - a.x, dy = b.y - a.y;
30 | return dr > 0 && dr * dr > dx * dx + dy * dy;
31 | }
32 |
33 | function score(node) {
34 | var a = node._,
35 | b = node.next._,
36 | ab = a.r + b.r,
37 | dx = (a.x * b.r + b.x * a.r) / ab,
38 | dy = (a.y * b.r + b.y * a.r) / ab;
39 | return dx * dx + dy * dy;
40 | }
41 |
42 | function Node(circle) {
43 | this._ = circle;
44 | this.next = null;
45 | this.previous = null;
46 | }
47 |
48 | export function packEnclose(circles) {
49 | if (!(n = (circles = array(circles)).length)) return 0;
50 |
51 | var a, b, c, n, aa, ca, i, j, k, sj, sk;
52 |
53 | // Place the first circle.
54 | a = circles[0], a.x = 0, a.y = 0;
55 | if (!(n > 1)) return a.r;
56 |
57 | // Place the second circle.
58 | b = circles[1], a.x = -b.r, b.x = a.r, b.y = 0;
59 | if (!(n > 2)) return a.r + b.r;
60 |
61 | // Place the third circle.
62 | place(b, a, c = circles[2]);
63 |
64 | // Initialize the front-chain using the first three circles a, b and c.
65 | a = new Node(a), b = new Node(b), c = new Node(c);
66 | a.next = c.previous = b;
67 | b.next = a.previous = c;
68 | c.next = b.previous = a;
69 |
70 | // Attempt to place each remaining circle…
71 | pack: for (i = 3; i < n; ++i) {
72 | place(a._, b._, c = circles[i]), c = new Node(c);
73 |
74 | // Find the closest intersecting circle on the front-chain, if any.
75 | // “Closeness” is determined by linear distance along the front-chain.
76 | // “Ahead” or “behind” is likewise determined by linear distance.
77 | j = b.next, k = a.previous, sj = b._.r, sk = a._.r;
78 | do {
79 | if (sj <= sk) {
80 | if (intersects(j._, c._)) {
81 | b = j, a.next = b, b.previous = a, --i;
82 | continue pack;
83 | }
84 | sj += j._.r, j = j.next;
85 | } else {
86 | if (intersects(k._, c._)) {
87 | a = k, a.next = b, b.previous = a, --i;
88 | continue pack;
89 | }
90 | sk += k._.r, k = k.previous;
91 | }
92 | } while (j !== k.next);
93 |
94 | // Success! Insert the new circle c between a and b.
95 | c.previous = a, c.next = b, a.next = b.previous = b = c;
96 |
97 | // Compute the new closest circle pair to the centroid.
98 | aa = score(a);
99 | while ((c = c.next) !== b) {
100 | if ((ca = score(c)) < aa) {
101 | a = c, aa = ca;
102 | }
103 | }
104 | b = a.next;
105 | }
106 |
107 | // Compute the enclosing circle of the front chain.
108 | a = [b._], c = b; while ((c = c.next) !== b) a.push(c._); c = enclose(a);
109 |
110 | // Translate the circles to put the enclosing circle around the origin.
111 | for (i = 0; i < n; ++i) a = circles[i], a.x -= c.x, a.y -= c.y;
112 |
113 | return c.r;
114 | }
115 |
116 | export default function(circles) {
117 | packEnclose(circles);
118 | return circles;
119 | }
120 |
--------------------------------------------------------------------------------
/test/pack/siblings-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3 = require("../../");
3 |
4 | tape("packSiblings(circles) produces a non-overlapping layout of circles", function(test) {
5 | permute([100, 200, 500, 70, 3].map(circleValue), p => intersectsAny(d3.packSiblings(p)) && test.fail(p.map(c => c.r)));
6 | permute([3, 30, 50, 400, 600].map(circleValue), p => intersectsAny(d3.packSiblings(p)) && test.fail(p.map(c => c.r)));
7 | permute([1, 1, 3, 30, 50, 400, 600].map(circleValue), p => intersectsAny(d3.packSiblings(p)) && test.fail(p.map(c => c.r)));
8 | test.equal(intersectsAny(d3.packSiblings([0.24155803737254639, 0.06349736576607135, 0.4721808601742349, 0.7469141449305542, 1.6399276349079663].map(circleRadius))), false);
9 | test.equal(intersectsAny(d3.packSiblings([2, 9071, 79, 51, 325, 867, 546, 19773, 371, 16, 165781, 10474, 6928, 40201, 31062, 14213, 8626, 12, 299, 1075, 98918, 4738, 664, 2694, 2619, 51237, 21431, 99, 5920, 1117, 321, 519162, 33559, 234, 4207].map(circleValue))), false);
10 | test.equal(intersectsAny(d3.packSiblings([0.3371386860049076, 58.65337373332081, 2.118883785686244, 1.7024669121097333, 5.834919697833051, 8.949453403094978, 6.792586534702093, 105.30490014617664, 6.058936212213754, 0.9535722042975694, 313.7636051642043].map(circleRadius))), false);
11 | test.equal(intersectsAny(d3.packSiblings([6.26551789195159, 1.707773433636342, 9.43220282933871, 9.298909705475646, 5.753163715613753, 8.882383159012575, 0.5819319661882536, 2.0234859171687747, 2.096171518434433, 9.762727931304937].map(circleRadius))), false);
12 | test.equal(intersectsAny(d3.packSiblings([9.153035316963035, 9.86048622524424, 8.3974499571329, 7.8338007571397865, 8.78260490259886, 6.165829618300345, 7.134819943097564, 7.803701771392344, 5.056638985134191, 7.424601077645588, 8.538658023474753, 2.4616388562274896, 0.5444633747829343, 9.005740508584667].map(circleRadius))), false);
13 | test.equal(intersectsAny(d3.packSiblings([2.23606797749979, 52.07088264296293, 5.196152422706632, 20.09975124224178, 357.11557267679996, 4.898979485566356, 14.7648230602334, 17.334875731491763].map(circleRadius))), false);
14 | test.end();
15 | });
16 |
17 | tape("packSiblings(circles) can successfully pack a circle with a tiny radius", function(test) {
18 | test.equal(intersectsAny(d3.packSiblings([
19 | 0.5672035864083508,
20 | 0.6363498687452267,
21 | 0.5628456216244132,
22 | 1.5619458670239148,
23 | 1.5658933259424268,
24 | 0.9195955097595698,
25 | 0.4747083763630309,
26 | 0.38341282734497434,
27 | 1.3475593361729394,
28 | 0.7492342961633259,
29 | 1.0716990115071823,
30 | 0.31686823341701664,
31 | 2.8766442376551415e-7
32 | ].map(circleRadius))), false);
33 | test.end();
34 | });
35 |
36 | tape("packSiblings accepts large circles", function(test) {
37 | test.deepEqual(d3.packSiblings([ {r: 1e+11}, {r: 1}, {r: 1} ]),
38 | [{r: 1e+11, x: 0, y: 0}, {r: 1, x: 1e+11 + 1, y: 0}, {r: 1, x: 1e+11 + 1, y: 2}]
39 | );
40 | test.deepEqual(d3.packSiblings([ {r: 1e+16}, {r: 1}, {r: 1} ]),
41 | [{r: 1e+16, x: 0, y: 0}, {r: 1, x: 1e+16 + 1, y: 0}, {r: 1, x: 1e+16 + 1, y: 2}]
42 | );
43 | test.end();
44 | });
45 |
46 | function swap(array, i, j) {
47 | var t = array[i];
48 | array[i] = array[j];
49 | array[j] = t;
50 | }
51 |
52 | function permute(array, f, n) {
53 | if (n == null) n = array.length;
54 | if (n === 1) return void f(array);
55 | for (var i = 0; i < n - 1; ++i) {
56 | permute(array, f, n - 1);
57 | swap(array, n & 1 ? 0 : i, n - 1);
58 | }
59 | permute(array, f, n - 1);
60 | }
61 |
62 | function circleValue(value) {
63 | return {r: Math.sqrt(value)};
64 | }
65 |
66 | function circleRadius(radius) {
67 | return {r: radius};
68 | }
69 |
70 | function intersectsAny(circles) {
71 | for (var i = 0, n = circles.length; i < n; ++i) {
72 | for (var j = i + 1, ci = circles[i]; j < n; ++j) {
73 | if (intersects(ci, circles[j])) {
74 | return true;
75 | }
76 | }
77 | }
78 | return false;
79 | }
80 |
81 | function intersects(a, b) {
82 | var dr = a.r + b.r - 1e-6, dx = b.x - a.x, dy = b.y - a.y;
83 | return dr > 0 && dr * dr > dx * dx + dy * dy;
84 | }
85 |
--------------------------------------------------------------------------------
/test/treemap/squarify-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../"),
3 | round = require("./round");
4 |
5 | tape("treemapSquarify(parent, x0, y0, x1, y1) generates a squarified layout", function(test) {
6 | var tile = d3_hierarchy.treemapSquarify,
7 | root = {
8 | value: 24,
9 | children: [
10 | {value: 6},
11 | {value: 6},
12 | {value: 4},
13 | {value: 3},
14 | {value: 2},
15 | {value: 2},
16 | {value: 1}
17 | ]
18 | };
19 | tile(root, 0, 0, 6, 4);
20 | test.deepEqual(root.children.map(round), [
21 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
22 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
23 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
24 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
25 | {x0: 3.00, x1: 5.40, y0: 2.33, y1: 3.17},
26 | {x0: 3.00, x1: 5.40, y0: 3.17, y1: 4.00},
27 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
28 | ]);
29 | test.end();
30 | });
31 |
32 | tape("treemapSquarify(parent, x0, y0, x1, y1) does not produce a stable update", function(test) {
33 | var tile = d3_hierarchy.treemapSquarify,
34 | root = {value: 20, children: [{value: 10}, {value: 10}]};
35 | tile(root, 0, 0, 20, 10);
36 | test.deepEqual(root.children.map(round), [
37 | {x0: 0, x1: 10, y0: 0, y1: 10},
38 | {x0: 10, x1: 20, y0: 0, y1: 10}
39 | ]);
40 | tile(root, 0, 0, 10, 20);
41 | test.deepEqual(root.children.map(round), [
42 | {x0: 0, x1: 10, y0: 0, y1: 10},
43 | {x0: 0, x1: 10, y0: 10, y1: 20}
44 | ]);
45 | test.end();
46 | });
47 |
48 | tape("treemapSquarify.ratio(ratio) observes the specified ratio", function(test) {
49 | var tile = d3_hierarchy.treemapSquarify.ratio(1),
50 | root = {
51 | value: 24,
52 | children: [
53 | {value: 6},
54 | {value: 6},
55 | {value: 4},
56 | {value: 3},
57 | {value: 2},
58 | {value: 2},
59 | {value: 1}
60 | ]
61 | };
62 | tile(root, 0, 0, 6, 4);
63 | test.deepEqual(root.children.map(round), [
64 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
65 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
66 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
67 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
68 | {x0: 3.00, x1: 4.20, y0: 2.33, y1: 4.00},
69 | {x0: 4.20, x1: 5.40, y0: 2.33, y1: 4.00},
70 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
71 | ]);
72 | test.end();
73 | });
74 |
75 | tape("treemapSquarify(parent, x0, y0, x1, y1) handles a degenerate tall empty parent", function(test) {
76 | var tile = d3_hierarchy.treemapSquarify,
77 | root = {
78 | value: 0,
79 | children: [
80 | {value: 0},
81 | {value: 0}
82 | ]
83 | };
84 | tile(root, 0, 0, 0, 4);
85 | test.deepEqual(root.children.map(round), [
86 | {x0: 0.00, x1: 0.00, y0: 0.00, y1: 4.00},
87 | {x0: 0.00, x1: 0.00, y0: 0.00, y1: 4.00}
88 | ]);
89 | test.end();
90 | });
91 |
92 | tape("treemapSquarify(parent, x0, y0, x1, y1) handles a degenerate wide empty parent", function(test) {
93 | var tile = d3_hierarchy.treemapSquarify,
94 | root = {
95 | value: 0,
96 | children: [
97 | {value: 0},
98 | {value: 0}
99 | ]
100 | };
101 | tile(root, 0, 0, 4, 0);
102 | test.deepEqual(root.children.map(round), [
103 | {x0: 0.00, x1: 4.00, y0: 0.00, y1: 0.00},
104 | {x0: 0.00, x1: 4.00, y0: 0.00, y1: 0.00}
105 | ]);
106 | test.end();
107 | });
108 |
109 | tape("treemapSquarify(parent, x0, y0, x1, y1) handles a leading zero value", function(test) {
110 | var tile = d3_hierarchy.treemapSquarify,
111 | root = {
112 | value: 24,
113 | children: [
114 | {value: 0},
115 | {value: 6},
116 | {value: 6},
117 | {value: 4},
118 | {value: 3},
119 | {value: 2},
120 | {value: 2},
121 | {value: 1}
122 | ]
123 | };
124 | tile(root, 0, 0, 6, 4);
125 | test.deepEqual(root.children.map(round), [
126 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 0.00},
127 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
128 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
129 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
130 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
131 | {x0: 3.00, x1: 5.40, y0: 2.33, y1: 3.17},
132 | {x0: 3.00, x1: 5.40, y0: 3.17, y1: 4.00},
133 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
134 | ]);
135 | test.end();
136 | });
137 |
--------------------------------------------------------------------------------
/src/tree.js:
--------------------------------------------------------------------------------
1 | import {Node} from "./hierarchy/index.js";
2 |
3 | function defaultSeparation(a, b) {
4 | return a.parent === b.parent ? 1 : 2;
5 | }
6 |
7 | // function radialSeparation(a, b) {
8 | // return (a.parent === b.parent ? 1 : 2) / a.depth;
9 | // }
10 |
11 | // This function is used to traverse the left contour of a subtree (or
12 | // subforest). It returns the successor of v on this contour. This successor is
13 | // either given by the leftmost child of v or by the thread of v. The function
14 | // returns null if and only if v is on the highest level of its subtree.
15 | function nextLeft(v) {
16 | var children = v.children;
17 | return children ? children[0] : v.t;
18 | }
19 |
20 | // This function works analogously to nextLeft.
21 | function nextRight(v) {
22 | var children = v.children;
23 | return children ? children[children.length - 1] : v.t;
24 | }
25 |
26 | // Shifts the current subtree rooted at w+. This is done by increasing
27 | // prelim(w+) and mod(w+) by shift.
28 | function moveSubtree(wm, wp, shift) {
29 | var change = shift / (wp.i - wm.i);
30 | wp.c -= change;
31 | wp.s += shift;
32 | wm.c += change;
33 | wp.z += shift;
34 | wp.m += shift;
35 | }
36 |
37 | // All other shifts, applied to the smaller subtrees between w- and w+, are
38 | // performed by this function. To prepare the shifts, we have to adjust
39 | // change(w+), shift(w+), and change(w-).
40 | function executeShifts(v) {
41 | var shift = 0,
42 | change = 0,
43 | children = v.children,
44 | i = children.length,
45 | w;
46 | while (--i >= 0) {
47 | w = children[i];
48 | w.z += shift;
49 | w.m += shift;
50 | shift += w.s + (change += w.c);
51 | }
52 | }
53 |
54 | // If vi-’s ancestor is a sibling of v, returns vi-’s ancestor. Otherwise,
55 | // returns the specified (default) ancestor.
56 | function nextAncestor(vim, v, ancestor) {
57 | return vim.a.parent === v.parent ? vim.a : ancestor;
58 | }
59 |
60 | function TreeNode(node, i) {
61 | this._ = node;
62 | this.parent = null;
63 | this.children = null;
64 | this.A = null; // default ancestor
65 | this.a = this; // ancestor
66 | this.z = 0; // prelim
67 | this.m = 0; // mod
68 | this.c = 0; // change
69 | this.s = 0; // shift
70 | this.t = null; // thread
71 | this.i = i; // number
72 | }
73 |
74 | TreeNode.prototype = Object.create(Node.prototype);
75 |
76 | function treeRoot(root) {
77 | var tree = new TreeNode(root, 0),
78 | node,
79 | nodes = [tree],
80 | child,
81 | children,
82 | i,
83 | n;
84 |
85 | while (node = nodes.pop()) {
86 | if (children = node._.children) {
87 | node.children = new Array(n = children.length);
88 | for (i = n - 1; i >= 0; --i) {
89 | nodes.push(child = node.children[i] = new TreeNode(children[i], i));
90 | child.parent = node;
91 | }
92 | }
93 | }
94 |
95 | (tree.parent = new TreeNode(null, 0)).children = [tree];
96 | return tree;
97 | }
98 |
99 | // Node-link tree diagram using the Reingold-Tilford "tidy" algorithm
100 | export default function() {
101 | var separation = defaultSeparation,
102 | dx = 1,
103 | dy = 1,
104 | nodeSize = null;
105 |
106 | function tree(root) {
107 | var t = treeRoot(root);
108 |
109 | // Compute the layout using Buchheim et al.’s algorithm.
110 | t.eachAfter(firstWalk), t.parent.m = -t.z;
111 | t.eachBefore(secondWalk);
112 |
113 | // If a fixed node size is specified, scale x and y.
114 | if (nodeSize) root.eachBefore(sizeNode);
115 |
116 | // If a fixed tree size is specified, scale x and y based on the extent.
117 | // Compute the left-most, right-most, and depth-most nodes for extents.
118 | else {
119 | var left = root,
120 | right = root,
121 | bottom = root;
122 | root.eachBefore(function(node) {
123 | if (node.x < left.x) left = node;
124 | if (node.x > right.x) right = node;
125 | if (node.depth > bottom.depth) bottom = node;
126 | });
127 | var s = left === right ? 1 : separation(left, right) / 2,
128 | tx = s - left.x,
129 | kx = dx / (right.x + s + tx),
130 | ky = dy / (bottom.depth || 1);
131 | root.eachBefore(function(node) {
132 | node.x = (node.x + tx) * kx;
133 | node.y = node.depth * ky;
134 | });
135 | }
136 |
137 | return root;
138 | }
139 |
140 | // Computes a preliminary x-coordinate for v. Before that, FIRST WALK is
141 | // applied recursively to the children of v, as well as the function
142 | // APPORTION. After spacing out the children by calling EXECUTE SHIFTS, the
143 | // node v is placed to the midpoint of its outermost children.
144 | function firstWalk(v) {
145 | var children = v.children,
146 | siblings = v.parent.children,
147 | w = v.i ? siblings[v.i - 1] : null;
148 | if (children) {
149 | executeShifts(v);
150 | var midpoint = (children[0].z + children[children.length - 1].z) / 2;
151 | if (w) {
152 | v.z = w.z + separation(v._, w._);
153 | v.m = v.z - midpoint;
154 | } else {
155 | v.z = midpoint;
156 | }
157 | } else if (w) {
158 | v.z = w.z + separation(v._, w._);
159 | }
160 | v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
161 | }
162 |
163 | // Computes all real x-coordinates by summing up the modifiers recursively.
164 | function secondWalk(v) {
165 | v._.x = v.z + v.parent.m;
166 | v.m += v.parent.m;
167 | }
168 |
169 | // The core of the algorithm. Here, a new subtree is combined with the
170 | // previous subtrees. Threads are used to traverse the inside and outside
171 | // contours of the left and right subtree up to the highest common level. The
172 | // vertices used for the traversals are vi+, vi-, vo-, and vo+, where the
173 | // superscript o means outside and i means inside, the subscript - means left
174 | // subtree and + means right subtree. For summing up the modifiers along the
175 | // contour, we use respective variables si+, si-, so-, and so+. Whenever two
176 | // nodes of the inside contours conflict, we compute the left one of the
177 | // greatest uncommon ancestors using the function ANCESTOR and call MOVE
178 | // SUBTREE to shift the subtree and prepare the shifts of smaller subtrees.
179 | // Finally, we add a new thread (if necessary).
180 | function apportion(v, w, ancestor) {
181 | if (w) {
182 | var vip = v,
183 | vop = v,
184 | vim = w,
185 | vom = vip.parent.children[0],
186 | sip = vip.m,
187 | sop = vop.m,
188 | sim = vim.m,
189 | som = vom.m,
190 | shift;
191 | while (vim = nextRight(vim), vip = nextLeft(vip), vim && vip) {
192 | vom = nextLeft(vom);
193 | vop = nextRight(vop);
194 | vop.a = v;
195 | shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
196 | if (shift > 0) {
197 | moveSubtree(nextAncestor(vim, v, ancestor), v, shift);
198 | sip += shift;
199 | sop += shift;
200 | }
201 | sim += vim.m;
202 | sip += vip.m;
203 | som += vom.m;
204 | sop += vop.m;
205 | }
206 | if (vim && !nextRight(vop)) {
207 | vop.t = vim;
208 | vop.m += sim - sop;
209 | }
210 | if (vip && !nextLeft(vom)) {
211 | vom.t = vip;
212 | vom.m += sip - som;
213 | ancestor = v;
214 | }
215 | }
216 | return ancestor;
217 | }
218 |
219 | function sizeNode(node) {
220 | node.x *= dx;
221 | node.y = node.depth * dy;
222 | }
223 |
224 | tree.separation = function(x) {
225 | return arguments.length ? (separation = x, tree) : separation;
226 | };
227 |
228 | tree.size = function(x) {
229 | return arguments.length ? (nodeSize = false, dx = +x[0], dy = +x[1], tree) : (nodeSize ? null : [dx, dy]);
230 | };
231 |
232 | tree.nodeSize = function(x) {
233 | return arguments.length ? (nodeSize = true, dx = +x[0], dy = +x[1], tree) : (nodeSize ? [dx, dy] : null);
234 | };
235 |
236 | return tree;
237 | }
238 |
--------------------------------------------------------------------------------
/test/treemap/index-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../../"),
3 | round = require("./round"),
4 | simple = require("../data/simple2");
5 |
6 | tape("treemap() has the expected defaults", function(test) {
7 | var treemap = d3_hierarchy.treemap();
8 | test.equal(treemap.tile(), d3_hierarchy.treemapSquarify);
9 | test.deepEqual(treemap.size(), [1, 1]);
10 | test.deepEqual(treemap.round(), false);
11 | test.end();
12 | });
13 |
14 | tape("treemap.round(round) observes the specified rounding", function(test) {
15 | var treemap = d3_hierarchy.treemap().size([600, 400]).round(true),
16 | root = treemap(d3_hierarchy.hierarchy(simple).sum(defaultValue).sort(descendingValue)),
17 | nodes = root.descendants().map(round);
18 | test.deepEqual(treemap.round(), true);
19 | test.deepEqual(nodes, [
20 | {x0: 0, x1: 600, y0: 0, y1: 400},
21 | {x0: 0, x1: 300, y0: 0, y1: 200},
22 | {x0: 0, x1: 300, y0: 200, y1: 400},
23 | {x0: 300, x1: 471, y0: 0, y1: 233},
24 | {x0: 471, x1: 600, y0: 0, y1: 233},
25 | {x0: 300, x1: 540, y0: 233, y1: 317},
26 | {x0: 300, x1: 540, y0: 317, y1: 400},
27 | {x0: 540, x1: 600, y0: 233, y1: 400}
28 | ]);
29 | test.end();
30 | });
31 |
32 | tape("treemap.round(round) coerces the specified round to boolean", function(test) {
33 | var treemap = d3_hierarchy.treemap().round("yes");
34 | test.strictEqual(treemap.round(), true);
35 | test.end();
36 | });
37 |
38 | tape("treemap.padding(padding) sets the inner and outer padding to the specified value", function(test) {
39 | var treemap = d3_hierarchy.treemap().padding("42");
40 | test.strictEqual(treemap.padding()(), 42);
41 | test.strictEqual(treemap.paddingInner()(), 42);
42 | test.strictEqual(treemap.paddingOuter()(), 42);
43 | test.strictEqual(treemap.paddingTop()(), 42);
44 | test.strictEqual(treemap.paddingRight()(), 42);
45 | test.strictEqual(treemap.paddingBottom()(), 42);
46 | test.strictEqual(treemap.paddingLeft()(), 42);
47 | test.end();
48 | });
49 |
50 | tape("treemap.paddingInner(padding) observes the specified padding", function(test) {
51 | var treemap = d3_hierarchy.treemap().size([6, 4]).paddingInner(0.5),
52 | root = treemap(d3_hierarchy.hierarchy(simple).sum(defaultValue).sort(descendingValue)),
53 | nodes = root.descendants().map(round);
54 | test.strictEqual(treemap.paddingInner()(), 0.5);
55 | test.deepEqual(treemap.size(), [6, 4]);
56 | test.deepEqual(nodes, [
57 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 4.00},
58 | {x0: 0.00, x1: 2.75, y0: 0.00, y1: 1.75},
59 | {x0: 0.00, x1: 2.75, y0: 2.25, y1: 4.00},
60 | {x0: 3.25, x1: 4.61, y0: 0.00, y1: 2.13},
61 | {x0: 5.11, x1: 6.00, y0: 0.00, y1: 2.13},
62 | {x0: 3.25, x1: 5.35, y0: 2.63, y1: 3.06},
63 | {x0: 3.25, x1: 5.35, y0: 3.56, y1: 4.00},
64 | {x0: 5.85, x1: 6.00, y0: 2.63, y1: 4.00}
65 | ]);
66 | test.end();
67 | });
68 |
69 | tape("treemap.paddingOuter(padding) observes the specified padding", function(test) {
70 | var treemap = d3_hierarchy.treemap().size([6, 4]).paddingOuter(0.5),
71 | root = treemap(d3_hierarchy.hierarchy(simple).sum(defaultValue).sort(descendingValue)),
72 | nodes = root.descendants().map(round);
73 | test.strictEqual(treemap.paddingOuter()(), 0.5);
74 | test.strictEqual(treemap.paddingTop()(), 0.5);
75 | test.strictEqual(treemap.paddingRight()(), 0.5);
76 | test.strictEqual(treemap.paddingBottom()(), 0.5);
77 | test.strictEqual(treemap.paddingLeft()(), 0.5);
78 | test.deepEqual(treemap.size(), [6, 4]);
79 | test.deepEqual(nodes, [
80 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 4.00},
81 | {x0: 0.50, x1: 3.00, y0: 0.50, y1: 2.00},
82 | {x0: 0.50, x1: 3.00, y0: 2.00, y1: 3.50},
83 | {x0: 3.00, x1: 4.43, y0: 0.50, y1: 2.25},
84 | {x0: 4.43, x1: 5.50, y0: 0.50, y1: 2.25},
85 | {x0: 3.00, x1: 5.00, y0: 2.25, y1: 2.88},
86 | {x0: 3.00, x1: 5.00, y0: 2.88, y1: 3.50},
87 | {x0: 5.00, x1: 5.50, y0: 2.25, y1: 3.50}
88 | ]);
89 | test.end();
90 | });
91 |
92 | tape("treemap.size(size) observes the specified size", function(test) {
93 | var treemap = d3_hierarchy.treemap().size([6, 4]),
94 | root = treemap(d3_hierarchy.hierarchy(simple).sum(defaultValue).sort(descendingValue)),
95 | nodes = root.descendants().map(round);
96 | test.deepEqual(treemap.size(), [6, 4]);
97 | test.deepEqual(nodes, [
98 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 4.00},
99 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
100 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
101 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
102 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
103 | {x0: 3.00, x1: 5.40, y0: 2.33, y1: 3.17},
104 | {x0: 3.00, x1: 5.40, y0: 3.17, y1: 4.00},
105 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
106 | ]);
107 | test.end();
108 | });
109 |
110 | tape("treemap.size(size) coerces the specified size to numbers", function(test) {
111 | var treemap = d3_hierarchy.treemap().size(["6", {valueOf: function() { return 4; }}]);
112 | test.strictEqual(treemap.size()[0], 6);
113 | test.strictEqual(treemap.size()[1], 4);
114 | test.end();
115 | });
116 |
117 | tape("treemap.size(size) makes defensive copies", function(test) {
118 | var size = [6, 4],
119 | treemap = d3_hierarchy.treemap().size(size),
120 | root = (size[1] = 100, treemap(d3_hierarchy.hierarchy(simple).sum(defaultValue).sort(descendingValue))),
121 | nodes = root.descendants().map(round);
122 | test.deepEqual(treemap.size(), [6, 4]);
123 | treemap.size()[1] = 100;
124 | test.deepEqual(treemap.size(), [6, 4]);
125 | test.deepEqual(nodes, [
126 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 4.00},
127 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
128 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
129 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
130 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
131 | {x0: 3.00, x1: 5.40, y0: 2.33, y1: 3.17},
132 | {x0: 3.00, x1: 5.40, y0: 3.17, y1: 4.00},
133 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
134 | ]);
135 | test.end();
136 | });
137 |
138 | tape("treemap.tile(tile) observes the specified tile function", function(test) {
139 | var treemap = d3_hierarchy.treemap().size([6, 4]).tile(d3_hierarchy.treemapSlice),
140 | root = treemap(d3_hierarchy.hierarchy(simple).sum(defaultValue).sort(descendingValue)),
141 | nodes = root.descendants().map(round);
142 | test.equal(treemap.tile(), d3_hierarchy.treemapSlice);
143 | test.deepEqual(nodes, [
144 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 4.00},
145 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 1.00},
146 | {x0: 0.00, x1: 6.00, y0: 1.00, y1: 2.00},
147 | {x0: 0.00, x1: 6.00, y0: 2.00, y1: 2.67},
148 | {x0: 0.00, x1: 6.00, y0: 2.67, y1: 3.17},
149 | {x0: 0.00, x1: 6.00, y0: 3.17, y1: 3.50},
150 | {x0: 0.00, x1: 6.00, y0: 3.50, y1: 3.83},
151 | {x0: 0.00, x1: 6.00, y0: 3.83, y1: 4.00}
152 | ]);
153 | test.end();
154 | });
155 |
156 | tape("treemap(data) observes the specified values", function(test) {
157 | var foo = function(d) { return d.foo; },
158 | treemap = d3_hierarchy.treemap().size([6, 4]),
159 | root = treemap(d3_hierarchy.hierarchy(require("../data/simple3")).sum(foo).sort(descendingValue)),
160 | nodes = root.descendants().map(round);
161 | test.deepEqual(treemap.size(), [6, 4]);
162 | test.deepEqual(nodes, [
163 | {x0: 0.00, x1: 6.00, y0: 0.00, y1: 4.00},
164 | {x0: 0.00, x1: 3.00, y0: 0.00, y1: 2.00},
165 | {x0: 0.00, x1: 3.00, y0: 2.00, y1: 4.00},
166 | {x0: 3.00, x1: 4.71, y0: 0.00, y1: 2.33},
167 | {x0: 4.71, x1: 6.00, y0: 0.00, y1: 2.33},
168 | {x0: 3.00, x1: 5.40, y0: 2.33, y1: 3.17},
169 | {x0: 3.00, x1: 5.40, y0: 3.17, y1: 4.00},
170 | {x0: 5.40, x1: 6.00, y0: 2.33, y1: 4.00}
171 | ]);
172 | test.end();
173 | });
174 |
175 | tape("treemap(data) observes the specified sibling order", function(test) {
176 | var treemap = d3_hierarchy.treemap(),
177 | root = treemap(d3_hierarchy.hierarchy(simple).sum(defaultValue).sort(ascendingValue));
178 | test.deepEqual(root.descendants().map(function(d) { return d.value; }), [24, 1, 2, 2, 3, 4, 6, 6]);
179 | test.end();
180 | });
181 |
182 | function defaultValue(d) {
183 | return d.value;
184 | }
185 |
186 | function ascendingValue(a, b) {
187 | return a.value - b.value;
188 | }
189 |
190 | function descendingValue(a, b) {
191 | return b.value - a.value;
192 | }
193 |
--------------------------------------------------------------------------------
/test/data/flare.csv:
--------------------------------------------------------------------------------
1 | id,value
2 | flare,
3 | flare.analytics,
4 | flare.analytics.cluster,
5 | flare.analytics.cluster.AgglomerativeCluster,3938
6 | flare.analytics.cluster.CommunityStructure,3812
7 | flare.analytics.cluster.HierarchicalCluster,6714
8 | flare.analytics.cluster.MergeEdge,743
9 | flare.analytics.graph,
10 | flare.analytics.graph.BetweennessCentrality,3534
11 | flare.analytics.graph.LinkDistance,5731
12 | flare.analytics.graph.MaxFlowMinCut,7840
13 | flare.analytics.graph.ShortestPaths,5914
14 | flare.analytics.graph.SpanningTree,3416
15 | flare.analytics.optimization,
16 | flare.analytics.optimization.AspectRatioBanker,7074
17 | flare.animate,
18 | flare.animate.Easing,17010
19 | flare.animate.FunctionSequence,5842
20 | flare.animate.interpolate,
21 | flare.animate.interpolate.ArrayInterpolator,1983
22 | flare.animate.interpolate.ColorInterpolator,2047
23 | flare.animate.interpolate.DateInterpolator,1375
24 | flare.animate.interpolate.Interpolator,8746
25 | flare.animate.interpolate.MatrixInterpolator,2202
26 | flare.animate.interpolate.NumberInterpolator,1382
27 | flare.animate.interpolate.ObjectInterpolator,1629
28 | flare.animate.interpolate.PointInterpolator,1675
29 | flare.animate.interpolate.RectangleInterpolator,2042
30 | flare.animate.ISchedulable,1041
31 | flare.animate.Parallel,5176
32 | flare.animate.Pause,449
33 | flare.animate.Scheduler,5593
34 | flare.animate.Sequence,5534
35 | flare.animate.Transition,9201
36 | flare.animate.Transitioner,19975
37 | flare.animate.TransitionEvent,1116
38 | flare.animate.Tween,6006
39 | flare.data,
40 | flare.data.converters,
41 | flare.data.converters.Converters,721
42 | flare.data.converters.DelimitedTextConverter,4294
43 | flare.data.converters.GraphMLConverter,9800
44 | flare.data.converters.IDataConverter,1314
45 | flare.data.converters.JSONConverter,2220
46 | flare.data.DataField,1759
47 | flare.data.DataSchema,2165
48 | flare.data.DataSet,586
49 | flare.data.DataSource,3331
50 | flare.data.DataTable,772
51 | flare.data.DataUtil,3322
52 | flare.display,
53 | flare.display.DirtySprite,8833
54 | flare.display.LineSprite,1732
55 | flare.display.RectSprite,3623
56 | flare.display.TextSprite,10066
57 | flare.flex,
58 | flare.flex.FlareVis,4116
59 | flare.physics,
60 | flare.physics.DragForce,1082
61 | flare.physics.GravityForce,1336
62 | flare.physics.IForce,319
63 | flare.physics.NBodyForce,10498
64 | flare.physics.Particle,2822
65 | flare.physics.Simulation,9983
66 | flare.physics.Spring,2213
67 | flare.physics.SpringForce,1681
68 | flare.query,
69 | flare.query.AggregateExpression,1616
70 | flare.query.And,1027
71 | flare.query.Arithmetic,3891
72 | flare.query.Average,891
73 | flare.query.BinaryExpression,2893
74 | flare.query.Comparison,5103
75 | flare.query.CompositeExpression,3677
76 | flare.query.Count,781
77 | flare.query.DateUtil,4141
78 | flare.query.Distinct,933
79 | flare.query.Expression,5130
80 | flare.query.ExpressionIterator,3617
81 | flare.query.Fn,3240
82 | flare.query.If,2732
83 | flare.query.IsA,2039
84 | flare.query.Literal,1214
85 | flare.query.Match,3748
86 | flare.query.Maximum,843
87 | flare.query.methods,
88 | flare.query.methods.add,593
89 | flare.query.methods.and,330
90 | flare.query.methods.average,287
91 | flare.query.methods.count,277
92 | flare.query.methods.distinct,292
93 | flare.query.methods.div,595
94 | flare.query.methods.eq,594
95 | flare.query.methods.fn,460
96 | flare.query.methods.gt,603
97 | flare.query.methods.gte,625
98 | flare.query.methods.iff,748
99 | flare.query.methods.isa,461
100 | flare.query.methods.lt,597
101 | flare.query.methods.lte,619
102 | flare.query.methods.max,283
103 | flare.query.methods.min,283
104 | flare.query.methods.mod,591
105 | flare.query.methods.mul,603
106 | flare.query.methods.neq,599
107 | flare.query.methods.not,386
108 | flare.query.methods.or,323
109 | flare.query.methods.orderby,307
110 | flare.query.methods.range,772
111 | flare.query.methods.select,296
112 | flare.query.methods.stddev,363
113 | flare.query.methods.sub,600
114 | flare.query.methods.sum,280
115 | flare.query.methods.update,307
116 | flare.query.methods.variance,335
117 | flare.query.methods.where,299
118 | flare.query.methods.xor,354
119 | flare.query.methods._,264
120 | flare.query.Minimum,843
121 | flare.query.Not,1554
122 | flare.query.Or,970
123 | flare.query.Query,13896
124 | flare.query.Range,1594
125 | flare.query.StringUtil,4130
126 | flare.query.Sum,791
127 | flare.query.Variable,1124
128 | flare.query.Variance,1876
129 | flare.query.Xor,1101
130 | flare.scale,
131 | flare.scale.IScaleMap,2105
132 | flare.scale.LinearScale,1316
133 | flare.scale.LogScale,3151
134 | flare.scale.OrdinalScale,3770
135 | flare.scale.QuantileScale,2435
136 | flare.scale.QuantitativeScale,4839
137 | flare.scale.RootScale,1756
138 | flare.scale.Scale,4268
139 | flare.scale.ScaleType,1821
140 | flare.scale.TimeScale,5833
141 | flare.util,
142 | flare.util.Arrays,8258
143 | flare.util.Colors,10001
144 | flare.util.Dates,8217
145 | flare.util.Displays,12555
146 | flare.util.Filter,2324
147 | flare.util.Geometry,10993
148 | flare.util.heap,
149 | flare.util.heap.FibonacciHeap,9354
150 | flare.util.heap.HeapNode,1233
151 | flare.util.IEvaluable,335
152 | flare.util.IPredicate,383
153 | flare.util.IValueProxy,874
154 | flare.util.math,
155 | flare.util.math.DenseMatrix,3165
156 | flare.util.math.IMatrix,2815
157 | flare.util.math.SparseMatrix,3366
158 | flare.util.Maths,17705
159 | flare.util.Orientation,1486
160 | flare.util.palette,
161 | flare.util.palette.ColorPalette,6367
162 | flare.util.palette.Palette,1229
163 | flare.util.palette.ShapePalette,2059
164 | flare.util.palette.SizePalette,2291
165 | flare.util.Property,5559
166 | flare.util.Shapes,19118
167 | flare.util.Sort,6887
168 | flare.util.Stats,6557
169 | flare.util.Strings,22026
170 | flare.vis,
171 | flare.vis.axis,
172 | flare.vis.axis.Axes,1302
173 | flare.vis.axis.Axis,24593
174 | flare.vis.axis.AxisGridLine,652
175 | flare.vis.axis.AxisLabel,636
176 | flare.vis.axis.CartesianAxes,6703
177 | flare.vis.controls,
178 | flare.vis.controls.AnchorControl,2138
179 | flare.vis.controls.ClickControl,3824
180 | flare.vis.controls.Control,1353
181 | flare.vis.controls.ControlList,4665
182 | flare.vis.controls.DragControl,2649
183 | flare.vis.controls.ExpandControl,2832
184 | flare.vis.controls.HoverControl,4896
185 | flare.vis.controls.IControl,763
186 | flare.vis.controls.PanZoomControl,5222
187 | flare.vis.controls.SelectionControl,7862
188 | flare.vis.controls.TooltipControl,8435
189 | flare.vis.data,
190 | flare.vis.data.Data,20544
191 | flare.vis.data.DataList,19788
192 | flare.vis.data.DataSprite,10349
193 | flare.vis.data.EdgeSprite,3301
194 | flare.vis.data.NodeSprite,19382
195 | flare.vis.data.render,
196 | flare.vis.data.render.ArrowType,698
197 | flare.vis.data.render.EdgeRenderer,5569
198 | flare.vis.data.render.IRenderer,353
199 | flare.vis.data.render.ShapeRenderer,2247
200 | flare.vis.data.ScaleBinding,11275
201 | flare.vis.data.Tree,7147
202 | flare.vis.data.TreeBuilder,9930
203 | flare.vis.events,
204 | flare.vis.events.DataEvent,2313
205 | flare.vis.events.SelectionEvent,1880
206 | flare.vis.events.TooltipEvent,1701
207 | flare.vis.events.VisualizationEvent,1117
208 | flare.vis.legend,
209 | flare.vis.legend.Legend,20859
210 | flare.vis.legend.LegendItem,4614
211 | flare.vis.legend.LegendRange,10530
212 | flare.vis.operator,
213 | flare.vis.operator.distortion,
214 | flare.vis.operator.distortion.BifocalDistortion,4461
215 | flare.vis.operator.distortion.Distortion,6314
216 | flare.vis.operator.distortion.FisheyeDistortion,3444
217 | flare.vis.operator.encoder,
218 | flare.vis.operator.encoder.ColorEncoder,3179
219 | flare.vis.operator.encoder.Encoder,4060
220 | flare.vis.operator.encoder.PropertyEncoder,4138
221 | flare.vis.operator.encoder.ShapeEncoder,1690
222 | flare.vis.operator.encoder.SizeEncoder,1830
223 | flare.vis.operator.filter,
224 | flare.vis.operator.filter.FisheyeTreeFilter,5219
225 | flare.vis.operator.filter.GraphDistanceFilter,3165
226 | flare.vis.operator.filter.VisibilityFilter,3509
227 | flare.vis.operator.IOperator,1286
228 | flare.vis.operator.label,
229 | flare.vis.operator.label.Labeler,9956
230 | flare.vis.operator.label.RadialLabeler,3899
231 | flare.vis.operator.label.StackedAreaLabeler,3202
232 | flare.vis.operator.layout,
233 | flare.vis.operator.layout.AxisLayout,6725
234 | flare.vis.operator.layout.BundledEdgeRouter,3727
235 | flare.vis.operator.layout.CircleLayout,9317
236 | flare.vis.operator.layout.CirclePackingLayout,12003
237 | flare.vis.operator.layout.DendrogramLayout,4853
238 | flare.vis.operator.layout.ForceDirectedLayout,8411
239 | flare.vis.operator.layout.IcicleTreeLayout,4864
240 | flare.vis.operator.layout.IndentedTreeLayout,3174
241 | flare.vis.operator.layout.Layout,7881
242 | flare.vis.operator.layout.NodeLinkTreeLayout,12870
243 | flare.vis.operator.layout.PieLayout,2728
244 | flare.vis.operator.layout.RadialTreeLayout,12348
245 | flare.vis.operator.layout.RandomLayout,870
246 | flare.vis.operator.layout.StackedAreaLayout,9121
247 | flare.vis.operator.layout.TreeMapLayout,9191
248 | flare.vis.operator.Operator,2490
249 | flare.vis.operator.OperatorList,5248
250 | flare.vis.operator.OperatorSequence,4190
251 | flare.vis.operator.OperatorSwitch,2581
252 | flare.vis.operator.SortOperator,2023
253 | flare.vis.Visualization,16540
254 |
--------------------------------------------------------------------------------
/test/pack/bench-enclose.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | var d3 = Object.assign({}, require("../../"), require("d3-array"), require("d3-random")),
4 | benchmark = require("benchmark");
5 |
6 | var slice = Array.prototype.slice,
7 | shuffle = d3.shuffle;
8 |
9 | var n = 0,
10 | m = 1000,
11 | r = d3.randomLogNormal(10),
12 | x = d3.randomUniform(0, 100),
13 | y = x,
14 | circles0,
15 | circles1;
16 |
17 | function extendBasis(B, p) {
18 | var i, j;
19 |
20 | if (enclosesWeakAll(p, B)) return [p];
21 |
22 | // If we get here then B must have at least one element.
23 | for (i = 0; i < B.length; ++i) {
24 | if (enclosesNot(p, B[i])
25 | && enclosesWeakAll(encloseBasis2(B[i], p), B)) {
26 | return [B[i], p];
27 | }
28 | }
29 |
30 | // If we get here then B must have at least two elements.
31 | for (i = 0; i < B.length - 1; ++i) {
32 | for (j = i + 1; j < B.length; ++j) {
33 | if (enclosesNot(encloseBasis2(B[i], B[j]), p)
34 | && enclosesNot(encloseBasis2(B[i], p), B[j])
35 | && enclosesNot(encloseBasis2(B[j], p), B[i])
36 | && enclosesWeakAll(encloseBasis3(B[i], B[j], p), B)) {
37 | return [B[i], B[j], p];
38 | }
39 | }
40 | }
41 |
42 | // If we get here then something is very wrong.
43 | throw new Error;
44 | }
45 |
46 | function enclosesNot(a, b) {
47 | var dr = a.r - b.r, dx = b.x - a.x, dy = b.y - a.y;
48 | return dr < 0 || dr * dr < dx * dx + dy * dy;
49 | }
50 |
51 | function enclosesWeak(a, b) {
52 | var dr = a.r - b.r + 1e-6, dx = b.x - a.x, dy = b.y - a.y;
53 | return dr > 0 && dr * dr > dx * dx + dy * dy;
54 | }
55 |
56 | function enclosesWeakAll(a, B) {
57 | for (var i = 0; i < B.length; ++i) {
58 | if (!enclosesWeak(a, B[i])) {
59 | return false;
60 | }
61 | }
62 | return true;
63 | }
64 |
65 | function encloseBasis(B) {
66 | switch (B.length) {
67 | case 1: return encloseBasis1(B[0]);
68 | case 2: return encloseBasis2(B[0], B[1]);
69 | case 3: return encloseBasis3(B[0], B[1], B[2]);
70 | }
71 | }
72 |
73 | function encloseBasis1(a) {
74 | return {
75 | x: a.x,
76 | y: a.y,
77 | r: a.r
78 | };
79 | }
80 |
81 | function encloseBasis2(a, b) {
82 | var x1 = a.x, y1 = a.y, r1 = a.r,
83 | x2 = b.x, y2 = b.y, r2 = b.r,
84 | x21 = x2 - x1, y21 = y2 - y1, r21 = r2 - r1,
85 | l = Math.sqrt(x21 * x21 + y21 * y21);
86 | return {
87 | x: (x1 + x2 + x21 / l * r21) / 2,
88 | y: (y1 + y2 + y21 / l * r21) / 2,
89 | r: (l + r1 + r2) / 2
90 | };
91 | }
92 |
93 | function encloseBasis3(a, b, c) {
94 | var x1 = a.x, y1 = a.y, r1 = a.r,
95 | x2 = b.x, y2 = b.y, r2 = b.r,
96 | x3 = c.x, y3 = c.y, r3 = c.r,
97 | a2 = x1 - x2,
98 | a3 = x1 - x3,
99 | b2 = y1 - y2,
100 | b3 = y1 - y3,
101 | c2 = r2 - r1,
102 | c3 = r3 - r1,
103 | d1 = x1 * x1 + y1 * y1 - r1 * r1,
104 | d2 = d1 - x2 * x2 - y2 * y2 + r2 * r2,
105 | d3 = d1 - x3 * x3 - y3 * y3 + r3 * r3,
106 | ab = a3 * b2 - a2 * b3,
107 | xa = (b2 * d3 - b3 * d2) / (ab * 2) - x1,
108 | xb = (b3 * c2 - b2 * c3) / ab,
109 | ya = (a3 * d2 - a2 * d3) / (ab * 2) - y1,
110 | yb = (a2 * c3 - a3 * c2) / ab,
111 | A = xb * xb + yb * yb - 1,
112 | B = 2 * (r1 + xa * xb + ya * yb),
113 | C = xa * xa + ya * ya - r1 * r1,
114 | r = -(A ? (B + Math.sqrt(B * B - 4 * A * C)) / (2 * A) : C / B);
115 | return {
116 | x: x1 + xa + xb * r,
117 | y: y1 + ya + yb * r,
118 | r: r
119 | };
120 | }
121 |
122 | function encloseCircular(L) {
123 | var i = 0, n = L.length, j = 0, B = [], p, e, k = 0;
124 |
125 | if (n) do {
126 | p = L[i];
127 | ++k;
128 | if (!(e && enclosesWeak(e, p))) {
129 | e = encloseBasis(B = extendBasis(B, p));
130 | j = i;
131 | }
132 | i = (i + 1) % n;
133 | } while (i != j);
134 |
135 | return e;
136 | }
137 |
138 | function encloseCircularShuffle(L) {
139 | var i = 0, n = shuffle(L = slice.call(L)).length, j = 0, B = [], p, e, k = 0;
140 |
141 | if (n) do {
142 | p = L[i];
143 | ++k;
144 | if (!(e && enclosesWeak(e, p))) {
145 | e = encloseBasis(B = extendBasis(B, p));
146 | j = i;
147 | }
148 | i = (i + 1) % n;
149 | } while (i != j);
150 |
151 | return e;
152 | }
153 |
154 | function encloseLazyShuffle(L) {
155 | var i = 0, j, n = (L = slice.call(L)).length, B = [], p, e;
156 |
157 | while (i < n) {
158 | p = L[j = i + (Math.random() * (n - i) | 0)], L[j] = L[i], L[i] = p;
159 | if (e && enclosesWeak(e, p)) ++i;
160 | else e = encloseBasis(B = extendBasis(B, p)), i = 0;
161 | }
162 |
163 | return e;
164 | }
165 |
166 | function encloseNoShuffle(L) {
167 | var i = 0, n = L.length, B = [], p, e;
168 |
169 | while (i < n) {
170 | p = L[i];
171 | if (e && enclosesWeak(e, p)) ++i;
172 | else e = encloseBasis(B = extendBasis(B, p)), i = 0;
173 | }
174 |
175 | return e;
176 | }
177 |
178 | function encloseShuffle(L) {
179 | var i = 0, n = shuffle(L = slice.call(L)).length, B = [], p, e;
180 |
181 | while (i < n) {
182 | p = L[i];
183 | if (e && enclosesWeak(e, p)) ++i;
184 | else e = encloseBasis(B = extendBasis(B, p)), i = 0;
185 | }
186 |
187 | return e;
188 | }
189 |
190 | function enclosePrePass(L) {
191 | var i, n = L.length, B = [], p, e;
192 |
193 | for (i = 0; i < n; ++i) {
194 | p = L[i];
195 | if (!(e && enclosesWeak(e, p))) e = encloseBasis(B = extendBasis(B, p));
196 | }
197 |
198 | for (i = 0; i < n;) {
199 | p = L[i];
200 | if (e && enclosesWeak(e, p)) ++i;
201 | else e = encloseBasis(B = extendBasis(B, p)), i = 0;
202 | }
203 |
204 | return e;
205 | }
206 |
207 | function enclosePrePassThenLazyShuffle(L) {
208 | var i, j, n = (L = slice.call(L)).length, B = [], p, e;
209 |
210 | for (i = 0; i < n; ++i) {
211 | p = L[i];
212 | if (!(e && enclosesWeak(e, p))) e = encloseBasis(B = extendBasis(B, p));
213 | }
214 |
215 | for (i = 0; i < n;) {
216 | p = L[j = i + (Math.random() * (n - i) | 0)], L[j] = L[i], L[i] = p;
217 | if (e && enclosesWeak(e, p)) ++i;
218 | else e = encloseBasis(B = extendBasis(B, p)), i = 0;
219 | }
220 |
221 | return e;
222 | }
223 |
224 | function encloseShufflePrePass(L) {
225 | var i, n = shuffle(L = slice.call(L)).length, B = [], p, e;
226 |
227 | for (i = 0; i < n; ++i) {
228 | p = L[i];
229 | if (!(e && enclosesWeak(e, p))) e = encloseBasis(B = extendBasis(B, p));
230 | }
231 |
232 | for (i = 0; i < n;) {
233 | p = L[i];
234 | if (e && enclosesWeak(e, p)) ++i;
235 | else e = encloseBasis(B = extendBasis(B, p)), i = 0;
236 | }
237 |
238 | return e;
239 | }
240 |
241 | function encloseCompletePasses(L) {
242 | var i, n = L.length, B = [], p, e, dirty = false;
243 |
244 | do {
245 | for (i = 0, dirty = false; i < n; ++i) {
246 | p = L[i];
247 | if (!(e && enclosesWeak(e, p))) e = encloseBasis(B = extendBasis(B, p)), dirty = true;
248 | }
249 | } while (dirty);
250 |
251 | return e;
252 | }
253 |
254 | function encloseShuffleCompletePasses(L) {
255 | var i, n = shuffle(L = slice.call(L)).length, B = [], p, e, dirty = false;
256 |
257 | do {
258 | for (i = 0, dirty = false; i < n; ++i) {
259 | p = L[i];
260 | if (!(e && enclosesWeak(e, p))) e = encloseBasis(B = extendBasis(B, p)), dirty = true;
261 | }
262 | } while (dirty);
263 |
264 | return e;
265 | }
266 |
267 | function recycle(event) {
268 | circles0 = d3.packSiblings(new Array(10).fill().map(() => ({r: r(), x: x(), y: y()})));
269 | circles1 = circles0.slice().reverse();
270 | }
271 |
272 | (new benchmark.Suite)
273 | .add("encloseNoShuffle (forward)", {onCycle: recycle, fn: () => encloseNoShuffle(circles0)})
274 | .add("encloseNoShuffle (reverse)", {onCycle: recycle, fn: () => encloseNoShuffle(circles1)})
275 | .add("enclosePrePass (forward)", {onCycle: recycle, fn: () => enclosePrePass(circles0)})
276 | .add("enclosePrePass (reverse)", {onCycle: recycle, fn: () => enclosePrePass(circles1)})
277 | .add("encloseCompletePasses (forward)", {onCycle: recycle, fn: () => encloseCompletePasses(circles0)})
278 | .add("encloseCompletePasses (reverse)", {onCycle: recycle, fn: () => encloseCompletePasses(circles1)})
279 | .add("encloseCircular (forward)", {onCycle: recycle, fn: () => encloseCircular(circles0)})
280 | .add("encloseCircular (reverse)", {onCycle: recycle, fn: () => encloseCircular(circles1)})
281 | .add("encloseShufflePrePass (forward)", {onCycle: recycle, fn: () => encloseShufflePrePass(circles0)})
282 | .add("encloseShufflePrePass (reverse)", {onCycle: recycle, fn: () => encloseShufflePrePass(circles1)})
283 | .add("encloseShuffleCompletePasses (forward)", {onCycle: recycle, fn: () => encloseShuffleCompletePasses(circles0)})
284 | .add("encloseShuffleCompletePasses (reverse)", {onCycle: recycle, fn: () => encloseShuffleCompletePasses(circles1)})
285 | .add("enclosePrePassThenLazyShuffle (forward)", {onCycle: recycle, fn: () => enclosePrePassThenLazyShuffle(circles0)})
286 | .add("enclosePrePassThenLazyShuffle (reverse)", {onCycle: recycle, fn: () => enclosePrePassThenLazyShuffle(circles1)})
287 | .add("encloseShuffle (forward)", {onCycle: recycle, fn: () => encloseShuffle(circles0)})
288 | .add("encloseShuffle (reverse)", {onCycle: recycle, fn: () => encloseShuffle(circles1)})
289 | .add("encloseLazyShuffle (forward)", {onCycle: recycle, fn: () => encloseLazyShuffle(circles0)})
290 | .add("encloseLazyShuffle (reverse)", {onCycle: recycle, fn: () => encloseLazyShuffle(circles1)})
291 | .add("encloseCircularShuffle (forward)", {onCycle: recycle, fn: () => encloseCircularShuffle(circles0)})
292 | .add("encloseCircularShuffle (reverse)", {onCycle: recycle, fn: () => encloseCircularShuffle(circles1)})
293 | .on("start", recycle)
294 | .on("cycle", event => console.log(event.target + ""))
295 | .run();
296 |
--------------------------------------------------------------------------------
/test/stratify-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape"),
2 | d3_hierarchy = require("../");
3 |
4 | tape("stratify() has the expected defaults", function(test) {
5 | var s = d3_hierarchy.stratify();
6 | test.equal(s.id()({id: "foo"}), "foo");
7 | test.equal(s.parentId()({parentId: "bar"}), "bar");
8 | test.end();
9 | });
10 |
11 | tape("stratify(data) returns the root node", function(test) {
12 | var s = d3_hierarchy.stratify(),
13 | root = s([
14 | {id: "a"},
15 | {id: "aa", parentId: "a"},
16 | {id: "ab", parentId: "a"},
17 | {id: "aaa", parentId: "aa"}
18 | ]);
19 | test.ok(root instanceof d3_hierarchy.hierarchy);
20 | test.deepEqual(noparent(root), {
21 | id: "a",
22 | depth: 0,
23 | height: 2,
24 | data: {id: "a"},
25 | children: [
26 | {
27 | id: "aa",
28 | depth: 1,
29 | height: 1,
30 | data: {id: "aa", parentId: "a"},
31 | children: [
32 | {
33 | id: "aaa",
34 | depth: 2,
35 | height: 0,
36 | data: {id: "aaa", parentId: "aa"}
37 | }
38 | ]
39 | },
40 | {
41 | id: "ab",
42 | depth: 1,
43 | height: 0,
44 | data: {id: "ab", parentId: "a"}
45 | }
46 | ]
47 | });
48 | test.end();
49 | });
50 |
51 | tape("stratify(data) does not require the data to be in topological order", function(test) {
52 | var s = d3_hierarchy.stratify(),
53 | root = s([
54 | {id: "aaa", parentId: "aa"},
55 | {id: "aa", parentId: "a"},
56 | {id: "ab", parentId: "a"},
57 | {id: "a"}
58 | ]);
59 | test.deepEqual(noparent(root), {
60 | id: "a",
61 | depth: 0,
62 | height: 2,
63 | data: {id: "a"},
64 | children: [
65 | {
66 | id: "aa",
67 | depth: 1,
68 | height: 1,
69 | data: {id: "aa", parentId: "a"},
70 | children: [
71 | {
72 | id: "aaa",
73 | depth: 2,
74 | height: 0,
75 | data: {id: "aaa", parentId: "aa"}
76 | }
77 | ]
78 | },
79 | {
80 | id: "ab",
81 | depth: 1,
82 | height: 0,
83 | data: {id: "ab", parentId: "a"}
84 | }
85 | ]
86 | });
87 | test.end();
88 | });
89 |
90 | tape("stratify(data) preserves the input order of siblings", function(test) {
91 | var s = d3_hierarchy.stratify(),
92 | root = s([
93 | {id: "aaa", parentId: "aa"},
94 | {id: "ab", parentId: "a"},
95 | {id: "aa", parentId: "a"},
96 | {id: "a"}
97 | ]);
98 | test.deepEqual(noparent(root), {
99 | id: "a",
100 | depth: 0,
101 | height: 2,
102 | data: {id: "a"},
103 | children: [
104 | {
105 | id: "ab",
106 | depth: 1,
107 | height: 0,
108 | data: {id: "ab", parentId: "a"}
109 | },
110 | {
111 | id: "aa",
112 | depth: 1,
113 | height: 1,
114 | data: {id: "aa", parentId: "a"},
115 | children: [
116 | {
117 | id: "aaa",
118 | depth: 2,
119 | height: 0,
120 | data: {id: "aaa", parentId: "aa"}
121 | }
122 | ]
123 | }
124 | ]
125 | });
126 | test.end();
127 | });
128 |
129 | tape("stratify(data) accepts an iterable", function(test) {
130 | var s = d3_hierarchy.stratify(),
131 | root = s(new Set([
132 | {id: "aaa", parentId: "aa"},
133 | {id: "ab", parentId: "a"},
134 | {id: "aa", parentId: "a"},
135 | {id: "a"}
136 | ]));
137 | test.deepEqual(noparent(root), {
138 | id: "a",
139 | depth: 0,
140 | height: 2,
141 | data: {id: "a"},
142 | children: [
143 | {
144 | id: "ab",
145 | depth: 1,
146 | height: 0,
147 | data: {id: "ab", parentId: "a"}
148 | },
149 | {
150 | id: "aa",
151 | depth: 1,
152 | height: 1,
153 | data: {id: "aa", parentId: "a"},
154 | children: [
155 | {
156 | id: "aaa",
157 | depth: 2,
158 | height: 0,
159 | data: {id: "aaa", parentId: "aa"}
160 | }
161 | ]
162 | }
163 | ]
164 | });
165 | test.end();
166 | });
167 |
168 | tape("stratify(data) treats an empty parentId as the root", function(test) {
169 | var s = d3_hierarchy.stratify(),
170 | root = s([
171 | {id: "a", parentId: ""},
172 | {id: "aa", parentId: "a"},
173 | {id: "ab", parentId: "a"},
174 | {id: "aaa", parentId: "aa"}
175 | ]);
176 | test.deepEqual(noparent(root), {
177 | id: "a",
178 | depth: 0,
179 | height: 2,
180 | data: {id: "a", parentId: ""},
181 | children: [
182 | {
183 | id: "aa",
184 | depth: 1,
185 | height: 1,
186 | data: {id: "aa", parentId: "a"},
187 | children: [
188 | {
189 | id: "aaa",
190 | depth: 2,
191 | height: 0,
192 | data: {id: "aaa", parentId: "aa"}
193 | }
194 | ]
195 | },
196 | {
197 | id: "ab",
198 | depth: 1,
199 | height: 0,
200 | data: {id: "ab", parentId: "a"}
201 | }
202 | ]
203 | });
204 | test.end();
205 | });
206 |
207 | tape("stratify(data) does not treat a falsy but non-empty parentId as the root", function(test) {
208 | var s = d3_hierarchy.stratify(),
209 | root = s([
210 | {id: 0, parentId: null},
211 | {id: 1, parentId: 0},
212 | {id: 2, parentId: 0}
213 | ]);
214 | test.deepEqual(noparent(root), {
215 | id: "0",
216 | depth: 0,
217 | height: 1,
218 | data: {id: 0, parentId: null},
219 | children: [
220 | {
221 | id: "1",
222 | depth: 1,
223 | height: 0,
224 | data: {id: 1, parentId: 0}
225 | },
226 | {
227 | id: "2",
228 | depth: 1,
229 | height: 0,
230 | data: {id: 2, parentId: 0}
231 | }
232 | ]
233 | });
234 | test.end();
235 | });
236 |
237 | tape("stratify(data) throws an error if the data does not have a single root", function(test) {
238 | var s = d3_hierarchy.stratify();
239 | test.throws(function() { s([{id: "a"}, {id: "b"}]); }, /\bmultiple roots\b/);
240 | test.throws(function() { s([{id: "a", parentId: "a"}]); }, /\bno root\b/);
241 | test.throws(function() { s([{id: "a", parentId: "b"}, {id: "b", parentId: "a"}]); }, /\bno root\b/);
242 | test.end();
243 | });
244 |
245 | tape("stratify(data) throws an error if the hierarchy is cyclical", function(test) {
246 | var s = d3_hierarchy.stratify();
247 | test.throws(function() { s([{id: "root"}, {id: "a", parentId: "a"}]); }, /\bcycle\b/);
248 | test.throws(function() { s([{id: "root"}, {id: "a", parentId: "b"}, {id: "b", parentId: "a"}]); }, /\bcycle\b/);
249 | test.end();
250 | });
251 |
252 | tape("stratify(data) throws an error if multiple parents have the same id", function(test) {
253 | var s = d3_hierarchy.stratify();
254 | test.throws(function() { s([{id: "a"}, {id: "b", parentId: "a"}, {id: "b", parentId: "a"}, {id: "c", parentId: "b"}]); }, /\bambiguous\b/);
255 | test.end();
256 | });
257 |
258 | tape("stratify(data) throws an error if the specified parent is not found", function(test) {
259 | var s = d3_hierarchy.stratify();
260 | test.throws(function() { s([{id: "a"}, {id: "b", parentId: "c"}]); }, /\bmissing\b/);
261 | test.end();
262 | });
263 |
264 | tape("stratify(data) allows the id to be undefined for leaf nodes", function(test) {
265 | var s = d3_hierarchy.stratify(),
266 | root = s([
267 | {id: "a"},
268 | {parentId: "a"},
269 | {parentId: "a"}
270 | ]);
271 | test.deepEqual(noparent(root), {
272 | id: "a",
273 | depth: 0,
274 | height: 1,
275 | data: {id: "a"},
276 | children: [
277 | {
278 | depth: 1,
279 | height: 0,
280 | data: {parentId: "a"}
281 | },
282 | {
283 | depth: 1,
284 | height: 0,
285 | data: {parentId: "a"}
286 | }
287 | ]
288 | });
289 | test.end();
290 | });
291 |
292 | tape("stratify(data) allows the id to be non-unique for leaf nodes", function(test) {
293 | var s = d3_hierarchy.stratify(),
294 | root = s([
295 | {id: "a", parentId: null},
296 | {id: "b", parentId: "a"},
297 | {id: "b", parentId: "a"}
298 | ]);
299 | test.deepEqual(noparent(root), {
300 | id: "a",
301 | depth: 0,
302 | height: 1,
303 | data: {id: "a", parentId: null},
304 | children: [
305 | {
306 | id: "b",
307 | depth: 1,
308 | height: 0,
309 | data: {id: "b", parentId: "a"}
310 | },
311 | {
312 | id: "b",
313 | depth: 1,
314 | height: 0,
315 | data: {id: "b", parentId: "a"}
316 | }
317 | ]
318 | });
319 | test.end();
320 | });
321 |
322 | tape("stratify(data) coerces the id to a string, if not null and not empty", function(test) {
323 | var s = d3_hierarchy.stratify();
324 | test.strictEqual(s([{id: {toString: function() { return "a"}}}]).id, "a");
325 | test.strictEqual(s([{id: ""}]).id, undefined);
326 | test.strictEqual(s([{id: null}]).id, undefined);
327 | test.strictEqual(s([{id: undefined}]).id, undefined);
328 | test.strictEqual(s([{}]).id, undefined);
329 | test.end();
330 | });
331 |
332 | tape("stratify(data) allows the id to be undefined for leaf nodes", function(test) {
333 | var s = d3_hierarchy.stratify(),
334 | o = {parentId: {toString: function() { return "a"; }}},
335 | root = s([{id: "a"}, o]);
336 | test.deepEqual(noparent(root), {
337 | id: "a",
338 | depth: 0,
339 | height: 1,
340 | data: {id: "a"},
341 | children: [
342 | {
343 | depth: 1,
344 | height: 0,
345 | data: o
346 | }
347 | ]
348 | });
349 | test.end();
350 | });
351 |
352 | tape("stratify.id(id) observes the specified id function", function(test) {
353 | var foo = function(d) { return d.foo; },
354 | s = d3_hierarchy.stratify().id(foo),
355 | root = s([
356 | {foo: "a"},
357 | {foo: "aa", parentId: "a"},
358 | {foo: "ab", parentId: "a"},
359 | {foo: "aaa", parentId: "aa"}
360 | ]);
361 | test.equal(s.id(), foo);
362 | test.deepEqual(noparent(root), {
363 | id: "a",
364 | depth: 0,
365 | height: 2,
366 | data: {foo: "a"},
367 | children: [
368 | {
369 | id: "aa",
370 | depth: 1,
371 | height: 1,
372 | data: {foo: "aa", parentId: "a"},
373 | children: [
374 | {
375 | id: "aaa",
376 | depth: 2,
377 | height: 0,
378 | data: {foo: "aaa", parentId:"aa" }
379 | }
380 | ]
381 | },
382 | {
383 | id: "ab",
384 | depth: 1,
385 | height: 0,
386 | data: {foo: "ab", parentId:"a" }
387 | }
388 | ]
389 | });
390 | test.end();
391 | });
392 |
393 | tape("stratify.id(id) tests that id is a function", function(test) {
394 | var s = d3_hierarchy.stratify();
395 | test.throws(function() { s.id(42); });
396 | test.throws(function() { s.id(null); });
397 | test.end();
398 | });
399 |
400 | tape("stratify.parentId(id) observes the specified parent id function", function(test) {
401 | var foo = function(d) { return d.foo; },
402 | s = d3_hierarchy.stratify().parentId(foo),
403 | root = s([
404 | {id: "a"},
405 | {id: "aa", foo: "a"},
406 | {id: "ab", foo: "a"},
407 | {id: "aaa", foo: "aa"}
408 | ]);
409 | test.equal(s.parentId(), foo);
410 | test.deepEqual(noparent(root), {
411 | id: "a",
412 | depth: 0,
413 | height: 2,
414 | data: {id: "a"},
415 | children: [
416 | {
417 | id: "aa",
418 | depth: 1,
419 | height: 1,
420 | data: {id: "aa", foo: "a"},
421 | children: [
422 | {
423 | id: "aaa",
424 | depth: 2,
425 | height: 0,
426 | data: {id: "aaa", foo: "aa"}
427 | }
428 | ]
429 | },
430 | {
431 | id: "ab",
432 | depth: 1,
433 | height: 0,
434 | data: {id: "ab", foo: "a"}
435 | }
436 | ]
437 | });
438 | test.end();
439 | });
440 |
441 | tape("stratify.parentId(id) tests that id is a function", function(test) {
442 | var s = d3_hierarchy.stratify();
443 | test.throws(function() { s.parentId(42); });
444 | test.throws(function() { s.parentId(null); });
445 | test.end();
446 | });
447 |
448 | function noparent(node) {
449 | var copy = {};
450 | for (var k in node) {
451 | if (node.hasOwnProperty(k)) { // eslint-disable-line no-prototype-builtins
452 | switch (k) {
453 | case "children": copy.children = node.children.map(noparent); break;
454 | case "parent": break;
455 | default: copy[k] = node[k]; break;
456 | }
457 | }
458 | }
459 | return copy;
460 | }
461 |
--------------------------------------------------------------------------------
/test/data/flare.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flare",
3 | "children": [
4 | {
5 | "name": "analytics",
6 | "children": [
7 | {
8 | "name": "cluster",
9 | "children": [
10 | {"name": "AgglomerativeCluster", "value": 3938},
11 | {"name": "CommunityStructure", "value": 3812},
12 | {"name": "HierarchicalCluster", "value": 6714},
13 | {"name": "MergeEdge", "value": 743}
14 | ]
15 | },
16 | {
17 | "name": "graph",
18 | "children": [
19 | {"name": "BetweennessCentrality", "value": 3534},
20 | {"name": "LinkDistance", "value": 5731},
21 | {"name": "MaxFlowMinCut", "value": 7840},
22 | {"name": "ShortestPaths", "value": 5914},
23 | {"name": "SpanningTree", "value": 3416}
24 | ]
25 | },
26 | {
27 | "name": "optimization",
28 | "children": [
29 | {"name": "AspectRatioBanker", "value": 7074}
30 | ]
31 | }
32 | ]
33 | },
34 | {
35 | "name": "animate",
36 | "children": [
37 | {"name": "Easing", "value": 17010},
38 | {"name": "FunctionSequence", "value": 5842},
39 | {
40 | "name": "interpolate",
41 | "children": [
42 | {"name": "ArrayInterpolator", "value": 1983},
43 | {"name": "ColorInterpolator", "value": 2047},
44 | {"name": "DateInterpolator", "value": 1375},
45 | {"name": "Interpolator", "value": 8746},
46 | {"name": "MatrixInterpolator", "value": 2202},
47 | {"name": "NumberInterpolator", "value": 1382},
48 | {"name": "ObjectInterpolator", "value": 1629},
49 | {"name": "PointInterpolator", "value": 1675},
50 | {"name": "RectangleInterpolator", "value": 2042}
51 | ]
52 | },
53 | {"name": "ISchedulable", "value": 1041},
54 | {"name": "Parallel", "value": 5176},
55 | {"name": "Pause", "value": 449},
56 | {"name": "Scheduler", "value": 5593},
57 | {"name": "Sequence", "value": 5534},
58 | {"name": "Transition", "value": 9201},
59 | {"name": "Transitioner", "value": 19975},
60 | {"name": "TransitionEvent", "value": 1116},
61 | {"name": "Tween", "value": 6006}
62 | ]
63 | },
64 | {
65 | "name": "data",
66 | "children": [
67 | {
68 | "name": "converters",
69 | "children": [
70 | {"name": "Converters", "value": 721},
71 | {"name": "DelimitedTextConverter", "value": 4294},
72 | {"name": "GraphMLConverter", "value": 9800},
73 | {"name": "IDataConverter", "value": 1314},
74 | {"name": "JSONConverter", "value": 2220}
75 | ]
76 | },
77 | {"name": "DataField", "value": 1759},
78 | {"name": "DataSchema", "value": 2165},
79 | {"name": "DataSet", "value": 586},
80 | {"name": "DataSource", "value": 3331},
81 | {"name": "DataTable", "value": 772},
82 | {"name": "DataUtil", "value": 3322}
83 | ]
84 | },
85 | {
86 | "name": "display",
87 | "children": [
88 | {"name": "DirtySprite", "value": 8833},
89 | {"name": "LineSprite", "value": 1732},
90 | {"name": "RectSprite", "value": 3623},
91 | {"name": "TextSprite", "value": 10066}
92 | ]
93 | },
94 | {
95 | "name": "flex",
96 | "children": [
97 | {"name": "FlareVis", "value": 4116}
98 | ]
99 | },
100 | {
101 | "name": "physics",
102 | "children": [
103 | {"name": "DragForce", "value": 1082},
104 | {"name": "GravityForce", "value": 1336},
105 | {"name": "IForce", "value": 319},
106 | {"name": "NBodyForce", "value": 10498},
107 | {"name": "Particle", "value": 2822},
108 | {"name": "Simulation", "value": 9983},
109 | {"name": "Spring", "value": 2213},
110 | {"name": "SpringForce", "value": 1681}
111 | ]
112 | },
113 | {
114 | "name": "query",
115 | "children": [
116 | {"name": "AggregateExpression", "value": 1616},
117 | {"name": "And", "value": 1027},
118 | {"name": "Arithmetic", "value": 3891},
119 | {"name": "Average", "value": 891},
120 | {"name": "BinaryExpression", "value": 2893},
121 | {"name": "Comparison", "value": 5103},
122 | {"name": "CompositeExpression", "value": 3677},
123 | {"name": "Count", "value": 781},
124 | {"name": "DateUtil", "value": 4141},
125 | {"name": "Distinct", "value": 933},
126 | {"name": "Expression", "value": 5130},
127 | {"name": "ExpressionIterator", "value": 3617},
128 | {"name": "Fn", "value": 3240},
129 | {"name": "If", "value": 2732},
130 | {"name": "IsA", "value": 2039},
131 | {"name": "Literal", "value": 1214},
132 | {"name": "Match", "value": 3748},
133 | {"name": "Maximum", "value": 843},
134 | {
135 | "name": "methods",
136 | "children": [
137 | {"name": "add", "value": 593},
138 | {"name": "and", "value": 330},
139 | {"name": "average", "value": 287},
140 | {"name": "count", "value": 277},
141 | {"name": "distinct", "value": 292},
142 | {"name": "div", "value": 595},
143 | {"name": "eq", "value": 594},
144 | {"name": "fn", "value": 460},
145 | {"name": "gt", "value": 603},
146 | {"name": "gte", "value": 625},
147 | {"name": "iff", "value": 748},
148 | {"name": "isa", "value": 461},
149 | {"name": "lt", "value": 597},
150 | {"name": "lte", "value": 619},
151 | {"name": "max", "value": 283},
152 | {"name": "min", "value": 283},
153 | {"name": "mod", "value": 591},
154 | {"name": "mul", "value": 603},
155 | {"name": "neq", "value": 599},
156 | {"name": "not", "value": 386},
157 | {"name": "or", "value": 323},
158 | {"name": "orderby", "value": 307},
159 | {"name": "range", "value": 772},
160 | {"name": "select", "value": 296},
161 | {"name": "stddev", "value": 363},
162 | {"name": "sub", "value": 600},
163 | {"name": "sum", "value": 280},
164 | {"name": "update", "value": 307},
165 | {"name": "variance", "value": 335},
166 | {"name": "where", "value": 299},
167 | {"name": "xor", "value": 354},
168 | {"name": "_", "value": 264}
169 | ]
170 | },
171 | {"name": "Minimum", "value": 843},
172 | {"name": "Not", "value": 1554},
173 | {"name": "Or", "value": 970},
174 | {"name": "Query", "value": 13896},
175 | {"name": "Range", "value": 1594},
176 | {"name": "StringUtil", "value": 4130},
177 | {"name": "Sum", "value": 791},
178 | {"name": "Variable", "value": 1124},
179 | {"name": "Variance", "value": 1876},
180 | {"name": "Xor", "value": 1101}
181 | ]
182 | },
183 | {
184 | "name": "scale",
185 | "children": [
186 | {"name": "IScaleMap", "value": 2105},
187 | {"name": "LinearScale", "value": 1316},
188 | {"name": "LogScale", "value": 3151},
189 | {"name": "OrdinalScale", "value": 3770},
190 | {"name": "QuantileScale", "value": 2435},
191 | {"name": "QuantitativeScale", "value": 4839},
192 | {"name": "RootScale", "value": 1756},
193 | {"name": "Scale", "value": 4268},
194 | {"name": "ScaleType", "value": 1821},
195 | {"name": "TimeScale", "value": 5833}
196 | ]
197 | },
198 | {
199 | "name": "util",
200 | "children": [
201 | {"name": "Arrays", "value": 8258},
202 | {"name": "Colors", "value": 10001},
203 | {"name": "Dates", "value": 8217},
204 | {"name": "Displays", "value": 12555},
205 | {"name": "Filter", "value": 2324},
206 | {"name": "Geometry", "value": 10993},
207 | {
208 | "name": "heap",
209 | "children": [
210 | {"name": "FibonacciHeap", "value": 9354},
211 | {"name": "HeapNode", "value": 1233}
212 | ]
213 | },
214 | {"name": "IEvaluable", "value": 335},
215 | {"name": "IPredicate", "value": 383},
216 | {"name": "IValueProxy", "value": 874},
217 | {
218 | "name": "math",
219 | "children": [
220 | {"name": "DenseMatrix", "value": 3165},
221 | {"name": "IMatrix", "value": 2815},
222 | {"name": "SparseMatrix", "value": 3366}
223 | ]
224 | },
225 | {"name": "Maths", "value": 17705},
226 | {"name": "Orientation", "value": 1486},
227 | {
228 | "name": "palette",
229 | "children": [
230 | {"name": "ColorPalette", "value": 6367},
231 | {"name": "Palette", "value": 1229},
232 | {"name": "ShapePalette", "value": 2059},
233 | {"name": "SizePalette", "value": 2291}
234 | ]
235 | },
236 | {"name": "Property", "value": 5559},
237 | {"name": "Shapes", "value": 19118},
238 | {"name": "Sort", "value": 6887},
239 | {"name": "Stats", "value": 6557},
240 | {"name": "Strings", "value": 22026}
241 | ]
242 | },
243 | {
244 | "name": "vis",
245 | "children": [
246 | {
247 | "name": "axis",
248 | "children": [
249 | {"name": "Axes", "value": 1302},
250 | {"name": "Axis", "value": 24593},
251 | {"name": "AxisGridLine", "value": 652},
252 | {"name": "AxisLabel", "value": 636},
253 | {"name": "CartesianAxes", "value": 6703}
254 | ]
255 | },
256 | {
257 | "name": "controls",
258 | "children": [
259 | {"name": "AnchorControl", "value": 2138},
260 | {"name": "ClickControl", "value": 3824},
261 | {"name": "Control", "value": 1353},
262 | {"name": "ControlList", "value": 4665},
263 | {"name": "DragControl", "value": 2649},
264 | {"name": "ExpandControl", "value": 2832},
265 | {"name": "HoverControl", "value": 4896},
266 | {"name": "IControl", "value": 763},
267 | {"name": "PanZoomControl", "value": 5222},
268 | {"name": "SelectionControl", "value": 7862},
269 | {"name": "TooltipControl", "value": 8435}
270 | ]
271 | },
272 | {
273 | "name": "data",
274 | "children": [
275 | {"name": "Data", "value": 20544},
276 | {"name": "DataList", "value": 19788},
277 | {"name": "DataSprite", "value": 10349},
278 | {"name": "EdgeSprite", "value": 3301},
279 | {"name": "NodeSprite", "value": 19382},
280 | {
281 | "name": "render",
282 | "children": [
283 | {"name": "ArrowType", "value": 698},
284 | {"name": "EdgeRenderer", "value": 5569},
285 | {"name": "IRenderer", "value": 353},
286 | {"name": "ShapeRenderer", "value": 2247}
287 | ]
288 | },
289 | {"name": "ScaleBinding", "value": 11275},
290 | {"name": "Tree", "value": 7147},
291 | {"name": "TreeBuilder", "value": 9930}
292 | ]
293 | },
294 | {
295 | "name": "events",
296 | "children": [
297 | {"name": "DataEvent", "value": 2313},
298 | {"name": "SelectionEvent", "value": 1880},
299 | {"name": "TooltipEvent", "value": 1701},
300 | {"name": "VisualizationEvent", "value": 1117}
301 | ]
302 | },
303 | {
304 | "name": "legend",
305 | "children": [
306 | {"name": "Legend", "value": 20859},
307 | {"name": "LegendItem", "value": 4614},
308 | {"name": "LegendRange", "value": 10530}
309 | ]
310 | },
311 | {
312 | "name": "operator",
313 | "children": [
314 | {
315 | "name": "distortion",
316 | "children": [
317 | {"name": "BifocalDistortion", "value": 4461},
318 | {"name": "Distortion", "value": 6314},
319 | {"name": "FisheyeDistortion", "value": 3444}
320 | ]
321 | },
322 | {
323 | "name": "encoder",
324 | "children": [
325 | {"name": "ColorEncoder", "value": 3179},
326 | {"name": "Encoder", "value": 4060},
327 | {"name": "PropertyEncoder", "value": 4138},
328 | {"name": "ShapeEncoder", "value": 1690},
329 | {"name": "SizeEncoder", "value": 1830}
330 | ]
331 | },
332 | {
333 | "name": "filter",
334 | "children": [
335 | {"name": "FisheyeTreeFilter", "value": 5219},
336 | {"name": "GraphDistanceFilter", "value": 3165},
337 | {"name": "VisibilityFilter", "value": 3509}
338 | ]
339 | },
340 | {"name": "IOperator", "value": 1286},
341 | {
342 | "name": "label",
343 | "children": [
344 | {"name": "Labeler", "value": 9956},
345 | {"name": "RadialLabeler", "value": 3899},
346 | {"name": "StackedAreaLabeler", "value": 3202}
347 | ]
348 | },
349 | {
350 | "name": "layout",
351 | "children": [
352 | {"name": "AxisLayout", "value": 6725},
353 | {"name": "BundledEdgeRouter", "value": 3727},
354 | {"name": "CircleLayout", "value": 9317},
355 | {"name": "CirclePackingLayout", "value": 12003},
356 | {"name": "DendrogramLayout", "value": 4853},
357 | {"name": "ForceDirectedLayout", "value": 8411},
358 | {"name": "IcicleTreeLayout", "value": 4864},
359 | {"name": "IndentedTreeLayout", "value": 3174},
360 | {"name": "Layout", "value": 7881},
361 | {"name": "NodeLinkTreeLayout", "value": 12870},
362 | {"name": "PieLayout", "value": 2728},
363 | {"name": "RadialTreeLayout", "value": 12348},
364 | {"name": "RandomLayout", "value": 870},
365 | {"name": "StackedAreaLayout", "value": 9121},
366 | {"name": "TreeMapLayout", "value": 9191}
367 | ]
368 | },
369 | {"name": "Operator", "value": 2490},
370 | {"name": "OperatorList", "value": 5248},
371 | {"name": "OperatorSequence", "value": 4190},
372 | {"name": "OperatorSwitch", "value": 2581},
373 | {"name": "SortOperator", "value": 2023}
374 | ]
375 | },
376 | {"name": "Visualization", "value": 16540}
377 | ]
378 | }
379 | ]
380 | }
381 |
--------------------------------------------------------------------------------
/test/data/flare-pack.json:
--------------------------------------------------------------------------------
1 | {"children":[{"children":[{"children":[{"children":[{"value":12870,"r":34.76,"x":95.67,"y":529.46,"name":"NodeLinkTreeLayout"},{"value":12348,"r":34.05,"x":164.49,"y":529.46,"name":"RadialTreeLayout"},{"value":12003,"r":33.57,"x":130.78,"y":588.08,"name":"CirclePackingLayout"},{"value":9317,"r":29.58,"x":130.74,"y":475.51,"name":"CircleLayout"},{"value":9191,"r":29.38,"x":72.06,"y":469.82,"name":"TreeMapLayout"},{"value":9121,"r":29.27,"x":189.43,"y":471.26,"name":"StackedAreaLayout"},{"value":8411,"r":28.1,"x":69.13,"y":586.44,"name":"ForceDirectedLayout"},{"value":7881,"r":27.2,"x":191.45,"y":584.46,"name":"Layout"},{"value":6725,"r":25.13,"x":38.22,"y":512.54,"name":"AxisLayout"},{"value":4864,"r":21.37,"x":217.53,"y":513.39,"name":"IcicleTreeLayout"},{"value":4853,"r":21.35,"x":28.66,"y":558.03,"name":"DendrogramLayout"},{"value":3727,"r":18.71,"x":224.72,"y":552.82,"name":"BundledEdgeRouter"},{"value":3174,"r":17.26,"x":104.75,"y":436.55,"name":"IndentedTreeLayout"},{"value":2728,"r":16,"x":157.83,"y":438.85,"name":"PieLayout"},{"value":870,"r":9.04,"x":95.75,"y":612.34,"name":"RandomLayout"}],"value":108083,"r":120.16,"x":124.8,"y":535.18,"name":"layout"},{"children":[{"value":9956,"r":30.58,"x":276.03,"y":530.85,"name":"Labeler"},{"value":3899,"r":19.13,"x":325.74,"y":530.85,"name":"RadialLabeler"},{"value":3202,"r":17.34,"x":310.59,"y":564.04,"name":"StackedAreaLabeler"}],"value":17057,"r":50.1,"x":295.07,"y":535.18,"name":"label"},{"children":[{"value":4138,"r":19.71,"x":255.27,"y":628.41,"name":"PropertyEncoder"},{"value":4060,"r":19.52,"x":294.51,"y":628.41,"name":"Encoder"},{"value":3179,"r":17.28,"x":275.07,"y":659.66,"name":"ColorEncoder"},{"value":1830,"r":13.11,"x":275.05,"y":602.22,"name":"SizeEncoder"},{"value":1690,"r":12.6,"x":249.98,"y":596.54,"name":"ShapeEncoder"}],"value":14897,"r":48.95,"x":265.11,"y":629.59,"name":"encoder"},{"children":[{"value":6314,"r":24.35,"x":243.38,"y":438.32,"name":"Distortion"},{"value":4461,"r":20.47,"x":288.19,"y":438.32,"name":"BifocalDistortion"},{"value":3444,"r":17.98,"x":269.29,"y":471.8,"name":"FisheyeDistortion"}],"value":14219,"r":45.7,"x":263.77,"y":444.64,"name":"distortion"},{"children":[{"value":5219,"r":22.14,"x":177.78,"y":382.14,"name":"FisheyeTreeFilter"},{"value":3509,"r":18.15,"x":218.07,"y":382.14,"name":"VisibilityFilter"},{"value":3165,"r":17.24,"x":201.62,"y":413.47,"name":"GraphDistanceFilter"}],"value":11893,"r":41.69,"x":195.79,"y":389.73,"name":"filter"},{"value":5248,"r":22.2,"x":199.25,"y":656.53,"name":"OperatorList"},{"value":4190,"r":19.84,"x":134.55,"y":395.52,"name":"OperatorSequence"},{"value":2581,"r":15.57,"x":162.58,"y":665.55,"name":"OperatorSwitch"},{"value":2490,"r":15.29,"x":317.38,"y":473.71,"name":"Operator"},{"value":2023,"r":13.78,"x":317.45,"y":595.02,"name":"SortOperator"},{"value":1286,"r":10.99,"x":136.03,"y":665.85,"name":"IOperator"}],"value":183967,"r":179.69,"x":183.79,"y":527.21,"name":"operator"},{"children":[{"value":20544,"r":43.92,"x":465.64,"y":523.73,"name":"Data"},{"value":19788,"r":43.11,"x":552.67,"y":523.73,"name":"DataList"},{"value":19382,"r":42.66,"x":509.96,"y":598.11,"name":"NodeSprite"},{"value":11275,"r":32.54,"x":509.87,"y":461.36,"name":"ScaleBinding"},{"value":10349,"r":31.17,"x":447.01,"y":450.99,"name":"DataSprite"},{"value":9930,"r":30.54,"x":572.35,"y":452.77,"name":"TreeBuilder"},{"children":[{"value":5569,"r":22.87,"x":415.39,"y":596.78,"name":"EdgeRenderer"},{"value":2247,"r":14.53,"x":452.79,"y":596.78,"name":"ShapeRenderer"},{"value":698,"r":8.1,"x":440.07,"y":615.49,"name":"ArrowType"},{"value":353,"r":5.76,"x":439.54,"y":581.42,"name":"IRenderer"}],"value":8867,"r":37.39,"x":429.92,"y":596.78,"name":"render"},{"value":7147,"r":25.91,"x":577.78,"y":588.01,"name":"Tree"},{"value":3301,"r":17.61,"x":415.43,"y":488.17,"name":"EdgeSprite"}],"value":110583,"r":135.24,"x":498.72,"y":527.21,"name":"data"},{"children":[{"value":8435,"r":28.14,"x":348.45,"y":706.7,"name":"TooltipControl"},{"value":7862,"r":27.17,"x":403.77,"y":706.7,"name":"SelectionControl"},{"value":5222,"r":22.14,"x":376.99,"y":748.1,"name":"PanZoomControl"},{"value":4896,"r":21.44,"x":376.97,"y":666.13,"name":"HoverControl"},{"value":4665,"r":20.93,"x":335.13,"y":659.47,"name":"ControlList"},{"value":3824,"r":18.95,"x":336.09,"y":752.14,"name":"ClickControl"},{"value":2832,"r":16.31,"x":414.69,"y":664.61,"name":"ExpandControl"},{"value":2649,"r":15.77,"x":414.9,"y":748.17,"name":"DragControl"},{"value":2138,"r":14.17,"x":438.27,"y":683.92,"name":"AnchorControl"},{"value":1353,"r":11.27,"x":434.6,"y":729.65,"name":"Control"},{"value":763,"r":8.46,"x":320.11,"y":729.86,"name":"IControl"}],"value":44639,"r":80.69,"x":374.88,"y":704.1,"name":"controls"},{"children":[{"value":20859,"r":44.26,"x":342.73,"y":356.97,"name":"Legend"},{"value":10530,"r":31.44,"x":418.43,"y":356.97,"name":"LegendRange"},{"value":4614,"r":20.81,"x":390.51,"y":401.15,"name":"LegendItem"}],"value":36003,"r":75.7,"x":374.17,"y":356.97,"name":"legend"},{"children":[{"value":24593,"r":48.05,"x":220.48,"y":282.04,"name":"Axis"},{"value":6703,"r":25.09,"x":293.62,"y":282.04,"name":"CartesianAxes"},{"value":1302,"r":11.06,"x":272.01,"y":311.01,"name":"Axes"},{"value":652,"r":7.82,"x":270.99,"y":258.15,"name":"AxisGridLine"},{"value":636,"r":7.73,"x":262.37,"y":245.2,"name":"AxisLabel"}],"value":33886,"r":73.14,"x":245.57,"y":282.04,"name":"axis"},{"value":16540,"r":39.41,"x":258.36,"y":733.24,"name":"Visualization"},{"children":[{"value":2313,"r":14.74,"x":467.14,"y":357.49,"name":"DataEvent"},{"value":1880,"r":13.29,"x":495.16,"y":357.49,"name":"SelectionEvent"},{"value":1701,"r":12.64,"x":482.53,"y":380.13,"name":"TooltipEvent"},{"value":1117,"r":10.24,"x":482.41,"y":337.72,"name":"VisualizationEvent"}],"value":7011,"r":32.64,"x":482.47,"y":360.12,"name":"events"}],"value":432629,"r":315.57,"x":318.94,"y":513.1,"name":"vis"},{"children":[{"value":22026,"r":45.48,"x":753.19,"y":509.88,"name":"Strings"},{"value":19118,"r":42.37,"x":841.03,"y":509.88,"name":"Shapes"},{"value":17705,"r":40.77,"x":800.11,"y":582.25,"name":"Maths"},{"value":12555,"r":34.33,"x":799.88,"y":445.15,"name":"Displays"},{"children":[{"value":6367,"r":24.45,"x":712.78,"y":424.58,"name":"ColorPalette"},{"value":2291,"r":14.67,"x":751.9,"y":424.58,"name":"SizePalette"},{"value":2059,"r":13.9,"x":740.71,"y":450.87,"name":"ShapePalette"},{"value":1229,"r":10.74,"x":739.92,"y":402.18,"name":"Palette"}],"value":11946,"r":39.81,"x":727.62,"y":428.52,"name":"palette"},{"value":10993,"r":32.13,"x":727.21,"y":583.01,"name":"Geometry"},{"children":[{"value":9354,"r":29.64,"x":862.99,"y":433.86,"name":"FibonacciHeap"},{"value":1233,"r":10.76,"x":903.39,"y":433.86,"name":"HeapNode"}],"value":10587,"r":40.4,"x":873.75,"y":433.86,"name":"heap"},{"value":10001,"r":30.64,"x":871.28,"y":576.34,"name":"Colors"},{"children":[{"value":3366,"r":17.78,"x":657.72,"y":475.96,"name":"SparseMatrix"},{"value":3165,"r":17.24,"x":692.74,"y":475.96,"name":"DenseMatrix"},{"value":2815,"r":16.26,"x":675.75,"y":504.83,"name":"IMatrix"}],"value":9346,"r":36.87,"x":674.93,"y":484.23,"name":"math"},{"value":8258,"r":27.85,"x":677.89,"y":548.88,"name":"Arrays"},{"value":8217,"r":27.78,"x":908.93,"y":492.25,"name":"Dates"},{"value":6887,"r":25.43,"x":917.61,"y":544.75,"name":"Sort"},{"value":6557,"r":24.81,"x":779.29,"y":389.7,"name":"Stats"},{"value":5559,"r":22.85,"x":826.92,"y":391.35,"name":"Property"},{"value":2324,"r":14.77,"x":757.95,"y":618.43,"name":"Filter"},{"value":1486,"r":11.81,"x":844.98,"y":609.67,"name":"Orientation"},{"value":874,"r":9.06,"x":779.87,"y":627.79,"name":"IValueProxy"},{"value":383,"r":6,"x":829.56,"y":618.58,"name":"IPredicate"},{"value":335,"r":5.61,"x":862.31,"y":611.46,"name":"IEvaluable"}],"value":165157,"r":156.21,"x":790.72,"y":513.1,"name":"util"},{"children":[{"children":[{"value":8746,"r":28.66,"x":609.83,"y":775.35,"name":"Interpolator"},{"value":2202,"r":14.38,"x":652.86,"y":775.35,"name":"MatrixInterpolator"},{"value":2047,"r":13.86,"x":643.08,"y":801.85,"name":"ColorInterpolator"},{"value":2042,"r":13.85,"x":643.08,"y":748.88,"name":"RectangleInterpolator"},{"value":1983,"r":13.65,"x":619.8,"y":734.25,"name":"ArrayInterpolator"},{"value":1675,"r":12.54,"x":620.3,"y":815.2,"name":"PointInterpolator"},{"value":1629,"r":12.37,"x":668.81,"y":753.88,"name":"ObjectInterpolator"},{"value":1382,"r":11.39,"x":667.74,"y":796.4,"name":"NumberInterpolator"},{"value":1375,"r":11.36,"x":596.49,"y":813.09,"name":"DateInterpolator"}],"value":23081,"r":58.97,"x":629.28,"y":778.57,"name":"interpolate"},{"value":19975,"r":43.31,"x":731.56,"y":778.57,"name":"Transitioner"},{"value":17010,"r":39.97,"x":694.37,"y":853.08,"name":"Easing"},{"value":9201,"r":29.39,"x":692.76,"y":717.09,"name":"Transition"},{"value":6006,"r":23.75,"x":643.52,"y":697.09,"name":"Tween"},{"value":5842,"r":23.42,"x":631.48,"y":860.93,"name":"FunctionSequence"},{"value":5593,"r":22.92,"x":744.96,"y":713.71,"name":"Scheduler"},{"value":5534,"r":22.8,"x":755.77,"y":840.08,"name":"Sequence"},{"value":5176,"r":22.05,"x":598.22,"y":703.74,"name":"Parallel"},{"value":1116,"r":10.24,"x":603.2,"y":842.68,"name":"TransitionEvent"},{"value":1041,"r":9.89,"x":673.9,"y":682.64,"name":"ISchedulable"},{"value":449,"r":6.49,"x":760.97,"y":738.38,"name":"Pause"}],"value":100024,"r":126.6,"x":677.28,"y":772.16,"name":"animate"},{"children":[{"children":[{"value":772,"r":8.51,"x":623,"y":278,"name":"range"},{"value":748,"r":8.38,"x":639.9,"y":278,"name":"iff"},{"value":625,"r":7.66,"x":631.58,"y":291.71,"name":"gte"},{"value":619,"r":7.62,"x":631.58,"y":264.33,"name":"lte"},{"value":603,"r":7.52,"x":616.46,"y":263.35,"name":"gt"},{"value":603,"r":7.52,"x":616.42,"y":292.62,"name":"mul"},{"value":600,"r":7.51,"x":646.69,"y":263.64,"name":"sub"},{"value":599,"r":7.5,"x":646.72,"y":292.34,"name":"neq"},{"value":597,"r":7.49,"x":607.25,"y":275.21,"name":"lt"},{"value":595,"r":7.47,"x":601.83,"y":289.15,"name":"div"},{"value":594,"r":7.47,"x":655.58,"y":275.69,"name":"eq"},{"value":593,"r":7.46,"x":661.4,"y":289.44,"name":"add"},{"value":591,"r":7.45,"x":624.75,"y":250.89,"name":"mod"},{"value":461,"r":6.58,"x":624.59,"y":304.12,"name":"isa"},{"value":460,"r":6.57,"x":638.73,"y":252.03,"name":"fn"},{"value":386,"r":6.02,"x":638.83,"y":303.31,"name":"not"},{"value":363,"r":5.84,"x":632.74,"y":313.49,"name":"stddev"},{"value":354,"r":5.77,"x":603.19,"y":262.59,"name":"xor"},{"value":335,"r":5.61,"x":659.8,"y":263.31,"name":"variance"},{"value":330,"r":5.57,"x":650.31,"y":304.9,"name":"and"},{"value":323,"r":5.51,"x":612.54,"y":305.06,"name":"or"},{"value":307,"r":5.37,"x":650.65,"y":251.39,"name":"orderby"},{"value":307,"r":5.37,"x":611.94,"y":251.28,"name":"update"},{"value":299,"r":5.3,"x":602.2,"y":301.92,"name":"where"},{"value":296,"r":5.27,"x":660.79,"y":302.16,"name":"select"},{"value":292,"r":5.24,"x":633.32,"y":241.53,"name":"distinct"},{"value":287,"r":5.19,"x":601.39,"y":251.78,"name":"average"},{"value":283,"r":5.15,"x":661.1,"y":252.63,"name":"max"},{"value":283,"r":5.15,"x":595.04,"y":278.5,"name":"min"},{"value":280,"r":5.13,"x":667.83,"y":278.61,"name":"sum"},{"value":277,"r":5.1,"x":669.18,"y":268.47,"name":"count"},{"value":264,"r":4.98,"x":594.16,"y":268.41,"name":"_"}],"value":14326,"r":44.17,"x":631.12,"y":276.99,"name":"methods"},{"value":13896,"r":36.12,"x":711.42,"y":276.99,"name":"Query"},{"value":5130,"r":21.95,"x":677.5,"y":324.13,"name":"Expression"},{"value":5103,"r":21.89,"x":677.49,"y":229.94,"name":"Comparison"},{"value":4141,"r":19.72,"x":639.22,"y":213.62,"name":"DateUtil"},{"value":4130,"r":19.69,"x":639.15,"y":340.36,"name":"StringUtil"},{"value":3891,"r":19.11,"x":717.74,"y":222.12,"name":"Arithmetic"},{"value":3748,"r":18.76,"x":717.53,"y":331.54,"name":"Match"},{"value":3677,"r":18.58,"x":601.75,"y":221.54,"name":"CompositeExpression"},{"value":3617,"r":18.43,"x":601.88,"y":332.35,"name":"ExpressionIterator"},{"value":3240,"r":17.44,"x":669.09,"y":191.51,"name":"Fn"},{"value":2893,"r":16.48,"x":668.51,"y":361.49,"name":"BinaryExpression"},{"value":2732,"r":16.02,"x":747.77,"y":314.37,"name":"If"},{"value":2039,"r":13.84,"x":745.26,"y":240.25,"name":"IsA"},{"value":1876,"r":13.27,"x":697.9,"y":356.85,"name":"Variance"},{"value":1616,"r":12.32,"x":698.25,"y":197.46,"name":"AggregateExpression"},{"value":1594,"r":12.23,"x":583.75,"y":307.62,"name":"Range"},{"value":1554,"r":12.08,"x":583.88,"y":246.46,"name":"Not"},{"value":1214,"r":10.68,"x":755.87,"y":262.35,"name":"Literal"},{"value":1124,"r":10.27,"x":756.11,"y":289.44,"name":"Variable"},{"value":1101,"r":10.17,"x":614.92,"y":357.8,"name":"Xor"},{"value":1027,"r":9.82,"x":643.47,"y":369.55,"name":"And"},{"value":970,"r":9.54,"x":615.23,"y":196.86,"name":"Or"},{"value":933,"r":9.36,"x":643.14,"y":184.8,"name":"Distinct"},{"value":891,"r":9.15,"x":578.72,"y":286.84,"name":"Average"},{"value":843,"r":8.9,"x":579.03,"y":266.86,"name":"Maximum"},{"value":843,"r":8.9,"x":769.84,"y":276.06,"name":"Minimum"},{"value":791,"r":8.62,"x":719.7,"y":358.83,"name":"Sum"},{"value":781,"r":8.56,"x":718.92,"y":194.47,"name":"Count"}],"value":89721,"r":107.82,"x":670.93,"y":277.82,"name":"query"},{"children":[{"children":[{"value":7840,"r":27.13,"x":450.08,"y":127.82,"name":"MaxFlowMinCut"},{"value":5914,"r":23.57,"x":500.78,"y":127.82,"name":"ShortestPaths"},{"value":5731,"r":23.2,"x":478.85,"y":169.12,"name":"LinkDistance"},{"value":3534,"r":18.22,"x":478.5,"y":92.48,"name":"BetweennessCentrality"},{"value":3416,"r":17.91,"x":443.57,"y":83.25,"name":"SpanningTree"}],"value":26435,"r":66.97,"x":462.21,"y":128.63,"name":"graph"},{"children":[{"value":6714,"r":25.11,"x":556.16,"y":120,"name":"HierarchicalCluster"},{"value":3938,"r":19.23,"x":600.49,"y":120,"name":"AgglomerativeCluster"},{"value":3812,"r":18.92,"x":583.77,"y":154.29,"name":"CommunityStructure"},{"value":743,"r":8.35,"x":582.37,"y":99.21,"name":"MergeEdge"}],"value":15207,"r":45.99,"x":575.17,"y":128.63,"name":"cluster"},{"children":[{"value":7074,"r":25.77,"x":533.97,"y":187.38,"name":"AspectRatioBanker"}],"value":7074,"r":25.77,"x":533.97,"y":187.38,"name":"optimization"}],"value":48716,"r":112.96,"x":508.2,"y":128.63,"name":"analytics"},{"children":[{"value":5833,"r":23.4,"x":483.77,"y":848.77,"name":"TimeScale"},{"value":4839,"r":21.32,"x":528.49,"y":848.77,"name":"QuantitativeScale"},{"value":4268,"r":20.02,"x":508.11,"y":884.73,"name":"Scale"},{"value":3770,"r":18.81,"x":508.05,"y":814.23,"name":"OrdinalScale"},{"value":3151,"r":17.2,"x":472.31,"y":809.82,"name":"LogScale"},{"value":2435,"r":15.12,"x":472.98,"y":885.75,"name":"QuantileScale"},{"value":2105,"r":14.06,"x":540.9,"y":815.64,"name":"IScaleMap"},{"value":1821,"r":13.08,"x":540.97,"y":880.82,"name":"ScaleType"},{"value":1756,"r":12.84,"x":452.37,"y":866.86,"name":"RootScale"},{"value":1316,"r":11.12,"x":453.95,"y":831.38,"name":"LinearScale"}],"value":31294,"r":65.15,"x":500.96,"y":847.5,"name":"scale"},{"children":[{"children":[{"value":9800,"r":30.33,"x":808.41,"y":292.56,"name":"GraphMLConverter"},{"value":4294,"r":20.08,"x":858.82,"y":292.56,"name":"DelimitedTextConverter"},{"value":2220,"r":14.44,"x":841.68,"y":322.52,"name":"JSONConverter"},{"value":1314,"r":11.11,"x":841,"y":266.97,"name":"IDataConverter"},{"value":721,"r":8.23,"x":824.21,"y":257.38,"name":"Converters"}],"value":18349,"r":50.41,"x":828.49,"y":292.56,"name":"converters"},{"value":3331,"r":17.69,"x":896.59,"y":292.56,"name":"DataSource"},{"value":3322,"r":17.66,"x":887.39,"y":326.69,"name":"DataUtil"},{"value":2165,"r":14.26,"x":885.76,"y":262.51,"name":"DataSchema"},{"value":1759,"r":12.85,"x":867.33,"y":242.62,"name":"DataField"},{"value":772,"r":8.51,"x":864.43,"y":339.26,"name":"DataTable"},{"value":586,"r":7.42,"x":847.6,"y":237.98,"name":"DataSet"}],"value":30284,"r":69.04,"x":846.96,"y":294.99,"name":"data"},{"children":[{"value":10498,"r":31.4,"x":349.07,"y":885.99,"name":"NBodyForce"},{"value":9983,"r":30.62,"x":411.09,"y":885.99,"name":"Simulation"},{"value":2822,"r":16.28,"x":380.68,"y":921.68,"name":"Particle"},{"value":2213,"r":14.42,"x":380.65,"y":852.8,"name":"Spring"},{"value":1681,"r":12.56,"x":355.7,"y":842.53,"name":"SpringForce"},{"value":1336,"r":11.2,"x":404.93,"y":844.62,"name":"GravityForce"},{"value":1082,"r":10.08,"x":354.87,"y":927.05,"name":"DragForce"},{"value":319,"r":5.47,"x":402.42,"y":921.02,"name":"IForce"}],"value":29934,"r":62.01,"x":379.69,"y":885.77,"name":"physics"},{"children":[{"value":10066,"r":30.74,"x":307.18,"y":138.37,"name":"TextSprite"},{"value":8833,"r":28.8,"x":366.72,"y":138.37,"name":"DirtySprite"},{"value":3623,"r":18.44,"x":338.52,"y":176.28,"name":"RectSprite"},{"value":1732,"r":12.75,"x":338.34,"y":108.03,"name":"LineSprite"}],"value":24254,"r":59.54,"x":335.97,"y":138.37,"name":"display"},{"children":[{"value":4116,"r":19.66,"x":797.48,"y":688.84,"name":"FlareVis"}],"value":4116,"r":19.66,"x":797.48,"y":688.84,"name":"flex"}],"value":956129,"x":480,"y":480,"r":480,"name":"flare"}
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # d3-hierarchy
2 |
3 | Many datasets are intrinsically hierarchical. Consider [geographic entities](https://www.census.gov/programs-surveys/geography/guidance/hierarchy.html), such as census blocks, census tracts, counties and states; the command structure of businesses and governments; file systems and software packages. And even non-hierarchical data may be arranged empirically into a hierarchy, as with [*k*-means clustering](https://en.wikipedia.org/wiki/K-means_clustering) or [phylogenetic trees](https://observablehq.com/@mbostock/tree-of-life).
4 |
5 | This module implements several popular techniques for visualizing hierarchical data:
6 |
7 | **Node-link diagrams** show topology using discrete marks for nodes and links, such as a circle for each node and a line connecting each parent and child. The [“tidy” tree](#tree) is delightfully compact, while the [dendrogram](#cluster) places leaves at the same level. (These have both polar and Cartesian forms.) [Indented trees](https://observablehq.com/@d3/indented-tree) are useful for interactive browsing.
8 |
9 | **Adjacency diagrams** show topology through the relative placement of nodes. They may also encode a quantitative dimension in the area of each node, for example to show revenue or file size. The [“icicle” diagram](#partition) uses rectangles, while the “sunburst” uses annular segments.
10 |
11 | **Enclosure diagrams** also use an area encoding, but show topology through containment. A [treemap](#treemap) recursively subdivides area into rectangles. [Circle-packing](#pack) tightly nests circles; this is not as space-efficient as a treemap, but perhaps more readily shows topology.
12 |
13 | A good hierarchical visualization facilitates rapid multiscale inference: micro-observations of individual elements and macro-observations of large groups.
14 |
15 | ## Installing
16 |
17 | 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:
18 |
19 | ```html
20 |
21 |
26 | ```
27 |
28 | ## API Reference
29 |
30 | * [Hierarchy](#hierarchy) ([Stratify](#stratify))
31 | * [Cluster](#cluster)
32 | * [Tree](#tree)
33 | * [Treemap](#treemap) ([Treemap Tiling](#treemap-tiling))
34 | * [Partition](#partition)
35 | * [Pack](#pack)
36 |
37 | ### Hierarchy
38 |
39 | Before you can compute a hierarchical layout, you need a root node. If your data is already in a hierarchical format, such as JSON, you can pass it directly to [d3.hierarchy](#hierarchy); otherwise, you can rearrange tabular data, such as comma-separated values (CSV), into a hierarchy using [d3.stratify](#stratify).
40 |
41 | # d3.hierarchy(data[, children]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/index.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)
42 |
43 | Constructs a root node from the specified hierarchical *data*. The specified *data* must be an object representing the root node. For example:
44 |
45 | ```json
46 | {
47 | "name": "Eve",
48 | "children": [
49 | {
50 | "name": "Cain"
51 | },
52 | {
53 | "name": "Seth",
54 | "children": [
55 | {
56 | "name": "Enos"
57 | },
58 | {
59 | "name": "Noam"
60 | }
61 | ]
62 | },
63 | {
64 | "name": "Abel"
65 | },
66 | {
67 | "name": "Awan",
68 | "children": [
69 | {
70 | "name": "Enoch"
71 | }
72 | ]
73 | },
74 | {
75 | "name": "Azura"
76 | }
77 | ]
78 | }
79 | ```
80 |
81 | 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:
82 |
83 | ```js
84 | function children(d) {
85 | return d.children;
86 | }
87 | ```
88 |
89 | If *data* is a Map, it is implicitly converted to the entry [undefined, *data*], and the children accessor instead defaults to:
90 |
91 | ```js
92 | function children(d) {
93 | return Array.isArray(d) ? d[1] : null;
94 | }
95 | ```
96 |
97 | 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.
98 |
99 | The returned node and each descendant has the following properties:
100 |
101 | * *node*.data - the associated data, as specified to the [constructor](#hierarchy).
102 | * *node*.depth - zero for the root node, and increasing by one for each descendant generation.
103 | * *node*.height - zero for leaf nodes, and the greatest distance from any descendant leaf for internal nodes.
104 | * *node*.parent - the parent node, or null for the root node.
105 | * *node*.children - an array of child nodes, if any; undefined for leaf nodes.
106 | * *node*.value - the summed value of the node and its [descendants](#node_descendants); optional, see [*node*.sum](#node_sum) and [*node*.count](#node_count).
107 |
108 | This method can also be used to test if a node is an `instanceof d3.hierarchy` and to extend the node prototype.
109 |
110 | # node.ancestors() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/ancestors.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)
111 |
112 | Returns the array of ancestors nodes, starting with this node, then followed by each parent up to the root.
113 |
114 | # node.descendants() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/descendants.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)
115 |
116 | Returns the array of descendant nodes, starting with this node, then followed by each child in topological order.
117 |
118 | # node.leaves() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/leaves.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)
119 |
120 | Returns the array of leaf nodes in traversal order; leaves are nodes with no children.
121 |
122 | # node.find(filter) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/find.js)
123 |
124 | 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.
125 |
126 | # node.path(target) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/path.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)
127 |
128 | 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).
129 |
130 | # node.links() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/links.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)
131 |
132 | Returns an array of links for this *node* and its descendants, where each *link* is an object that defines source and target properties. The source of each link is the parent node, and the target is a child node.
133 |
134 | # node.sum(value) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/sum.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
135 |
136 | Evaluates the specified *value* function for this *node* and each descendant in [post-order traversal](#node_eachAfter), and returns this *node*. The *node*.value property of each node is set to the numeric value returned by the specified function plus the combined value of all children. The function is passed the node’s data, and must return a non-negative number. The *value* accessor is evaluated for *node* and every descendant, including internal nodes; if you only want leaf nodes to have internal value, then return zero for any node with children. [For example](https://observablehq.com/@d3/treemap-by-count), as an alternative to [*node*.count](#node_count):
137 |
138 | ```js
139 | root.sum(function(d) { return d.value ? 1 : 0; });
140 | ```
141 |
142 | You must call *node*.sum or [*node*.count](#node_count) before invoking a hierarchical layout that requires *node*.value, such as [d3.treemap](#treemap). Since the API supports [method chaining](https://en.wikipedia.org/wiki/Method_chaining), you can invoke *node*.sum and [*node*.sort](#node_sort) before computing the layout, and then subsequently generate an array of all [descendant nodes](#node_descendants) like so:
143 |
144 | ```js
145 | var treemap = d3.treemap()
146 | .size([width, height])
147 | .padding(2);
148 |
149 | var nodes = treemap(root
150 | .sum(function(d) { return d.value; })
151 | .sort(function(a, b) { return b.height - a.height || b.value - a.value; }))
152 | .descendants();
153 | ```
154 |
155 | This example assumes that the node data has a value field.
156 |
157 | # node.count() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/count.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
158 |
159 | Computes the number of leaves under this *node* and assigns it to *node*.value, and similarly for every descendant of *node*. If this *node* is a leaf, its count is one. Returns this *node*. See also [*node*.sum](#node_sum).
160 |
161 | # node.sort(compare) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/sort.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
162 |
163 | Sorts the children of this *node*, if any, and each of this *node*’s descendants’ children, in [pre-order traversal](#node_eachBefore) using the specified *compare* function, and returns this *node*. The specified function is passed two nodes *a* and *b* to compare. If *a* should be before *b*, the function must return a value less than zero; if *b* should be before *a*, the function must return a value greater than zero; otherwise, the relative order of *a* and *b* are not specified. See [*array*.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) for more.
164 |
165 | Unlike [*node*.sum](#node_sum), the *compare* function is passed two [nodes](#hierarchy) rather than two nodes’ data. For example, if the data has a value property, this sorts nodes by the descending aggregate value of the node and all its descendants, as is recommended for [circle-packing](#pack):
166 |
167 | ```js
168 | root
169 | .sum(function(d) { return d.value; })
170 | .sort(function(a, b) { return b.value - a.value; });
171 | ``````
172 |
173 | Similarly, to sort nodes by descending height (greatest distance from any descendant leaf) and then descending value, as is recommended for [treemaps](#treemap) and [icicles](#partition):
174 |
175 | ```js
176 | root
177 | .sum(function(d) { return d.value; })
178 | .sort(function(a, b) { return b.height - a.height || b.value - a.value; });
179 | ```
180 |
181 | To sort nodes by descending height and then ascending id, as is recommended for [trees](#tree) and [dendrograms](#cluster):
182 |
183 | ```js
184 | root
185 | .sum(function(d) { return d.value; })
186 | .sort(function(a, b) { return b.height - a.height || a.id.localeCompare(b.id); });
187 | ```
188 |
189 | 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.
190 |
191 | # node\[Symbol.iterator\]() [<>](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/iterator.js "Source")
192 |
193 | Returns an iterator over the *node*’s descendants in breadth-first order. For example:
194 |
195 | ```js
196 | for (const descendant of node) {
197 | console.log(descendant);
198 | }
199 | ```
200 |
201 | # node.each(function[, that]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/each.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
202 |
203 | 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.
204 |
205 | # node.eachAfter(function[, that]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/eachAfter.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
206 |
207 | 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.
208 |
209 | # node.eachBefore(function[, that]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/eachBefore.js), [Examples](https://observablehq.com/@d3/visiting-a-d3-hierarchy)
210 |
211 | 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.
212 |
213 | # node.copy() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/hierarchy/index.js), [Examples](https://observablehq.com/@d3/d3-hierarchy)
214 |
215 | Return a deep copy of the subtree starting at this *node*. (The returned deep copy shares the same data, however.) The returned node is the root of a new tree; the returned node’s parent is always null and its depth is always zero.
216 |
217 | #### Stratify
218 |
219 | Consider the following table of relationships:
220 |
221 | Name | Parent
222 | ------|--------
223 | Eve |
224 | Cain | Eve
225 | Seth | Eve
226 | Enos | Seth
227 | Noam | Seth
228 | Abel | Eve
229 | Awan | Eve
230 | Enoch | Awan
231 | Azura | Eve
232 |
233 | These names are conveniently unique, so we can unambiguously represent the hierarchy as a CSV file:
234 |
235 | ```
236 | name,parent
237 | Eve,
238 | Cain,Eve
239 | Seth,Eve
240 | Enos,Seth
241 | Noam,Seth
242 | Abel,Eve
243 | Awan,Eve
244 | Enoch,Awan
245 | Azura,Eve
246 | ```
247 |
248 | To parse the CSV using [d3.csvParse](https://github.com/d3/d3-dsv#csvParse):
249 |
250 | ```js
251 | var table = d3.csvParse(text);
252 | ```
253 |
254 | This returns:
255 |
256 | ```json
257 | [
258 | {"name": "Eve", "parent": ""},
259 | {"name": "Cain", "parent": "Eve"},
260 | {"name": "Seth", "parent": "Eve"},
261 | {"name": "Enos", "parent": "Seth"},
262 | {"name": "Noam", "parent": "Seth"},
263 | {"name": "Abel", "parent": "Eve"},
264 | {"name": "Awan", "parent": "Eve"},
265 | {"name": "Enoch", "parent": "Awan"},
266 | {"name": "Azura", "parent": "Eve"}
267 | ]
268 | ```
269 |
270 | To convert to a hierarchy:
271 |
272 | ```js
273 | var root = d3.stratify()
274 | .id(function(d) { return d.name; })
275 | .parentId(function(d) { return d.parent; })
276 | (table);
277 | ```
278 |
279 | This returns:
280 |
281 | [
](https://runkit.com/mbostock/56fed33d8630b01300f72daa)
282 |
283 | This hierarchy can now be passed to a hierarchical layout, such as [d3.tree](#_tree), for visualization.
284 |
285 | # d3.stratify() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/stratify.js), [Examples](https://observablehq.com/@d3/d3-stratify)
286 |
287 | Constructs a new stratify operator with the default settings.
288 |
289 | # stratify(data) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/stratify.js), [Examples](https://observablehq.com/@d3/d3-stratify)
290 |
291 | Generates a new hierarchy from the specified tabular *data*.
292 |
293 | # stratify.id([id]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/stratify.js), [Examples](https://observablehq.com/@d3/d3-stratify)
294 |
295 | If *id* is specified, sets the id accessor to the given function and returns this stratify operator. Otherwise, returns the current id accessor, which defaults to:
296 |
297 | ```js
298 | function id(d) {
299 | return d.id;
300 | }
301 | ```
302 |
303 | The id accessor is invoked for each element in the input data passed to the [stratify operator](#_stratify), being passed the current datum (*d*) and the current index (*i*). The returned string is then used to identify the node’s relationships in conjunction with the [parent id](#stratify_parentId). For leaf nodes, the id may be undefined; otherwise, the id must be unique. (Null and the empty string are equivalent to undefined.)
304 |
305 | # stratify.parentId([parentId]) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/stratify.js), [Examples](https://observablehq.com/@d3/d3-stratify)
306 |
307 | If *parentId* is specified, sets the parent id accessor to the given function and returns this stratify operator. Otherwise, returns the current parent id accessor, which defaults to:
308 |
309 | ```js
310 | function parentId(d) {
311 | return d.parentId;
312 | }
313 | ```
314 |
315 | The parent id accessor is invoked for each element in the input data passed to the [stratify operator](#_stratify), being passed the current datum (*d*) and the current index (*i*). The returned string is then used to identify the node’s relationships in conjunction with the [id](#stratify_id). For the root node, the parent id should be undefined. (Null and the empty string are equivalent to undefined.) There must be exactly one root node in the input data, and no circular relationships.
316 |
317 | ### Cluster
318 |
319 | [
](https://observablehq.com/@d3/cluster-dendrogram)
320 |
321 | The **cluster layout** produces [dendrograms](http://en.wikipedia.org/wiki/Dendrogram): node-link diagrams that place leaf nodes of the tree at the same depth. Dendrograms are typically less compact than [tidy trees](#tree), but are useful when all the leaves should be at the same level, such as for hierarchical clustering or [phylogenetic tree diagrams](https://observablehq.com/@mbostock/tree-of-life).
322 |
323 | # d3.cluster() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/cluster.js), [Examples](https://observablehq.com/@d3/cluster-dendrogram)
324 |
325 | Creates a new cluster layout with default settings.
326 |
327 | # cluster(root)
328 |
329 | Lays out the specified *root* [hierarchy](#hierarchy), assigning the following properties on *root* and its descendants:
330 |
331 | * *node*.x - the *x*-coordinate of the node
332 | * *node*.y - the *y*-coordinate of the node
333 |
334 | The coordinates *x* and *y* represent an arbitrary coordinate system; for example, you can treat *x* as an angle and *y* as a radius to produce a [radial layout](https://observablehq.com/@d3/radial-dendrogram). You may want to call [*root*.sort](#node_sort) before passing the hierarchy to the cluster layout.
335 |
336 | # cluster.size([size])
337 |
338 | If *size* is specified, sets this cluster layout’s size to the specified two-element array of numbers [*width*, *height*] and returns this cluster layout. If *size* is not specified, returns the current layout size, which defaults to [1, 1]. A layout size of null indicates that a [node size](#cluster_nodeSize) will be used instead. The coordinates *x* and *y* represent an arbitrary coordinate system; for example, to produce a [radial layout](https://observablehq.com/@d3/radial-dendrogram), a size of [360, *radius*] corresponds to a breadth of 360° and a depth of *radius*.
339 |
340 | # cluster.nodeSize([size])
341 |
342 | If *size* is specified, sets this cluster layout’s node size to the specified two-element array of numbers [*width*, *height*] and returns this cluster layout. If *size* is not specified, returns the current node size, which defaults to null. A node size of null indicates that a [layout size](#cluster_size) will be used instead. When a node size is specified, the root node is always positioned at ⟨0, 0⟩.
343 |
344 | # cluster.separation([separation])
345 |
346 | If *separation* is specified, sets the separation accessor to the specified function and returns this cluster layout. If *separation* is not specified, returns the current separation accessor, which defaults to:
347 |
348 | ```js
349 | function separation(a, b) {
350 | return a.parent == b.parent ? 1 : 2;
351 | }
352 | ```
353 |
354 | The separation accessor is used to separate neighboring leaves. The separation function is passed two leaves *a* and *b*, and must return the desired separation. The nodes are typically siblings, though the nodes may be more distantly related if the layout decides to place such nodes adjacent.
355 |
356 | ### Tree
357 |
358 | [
](https://observablehq.com/@d3/tidy-tree)
359 |
360 | The **tree** layout produces tidy node-link diagrams of trees using the [Reingold–Tilford “tidy” algorithm](http://reingold.co/tidier-drawings.pdf), improved to run in linear time by [Buchheim *et al.*](http://dirk.jivas.de/papers/buchheim02improving.pdf) Tidy trees are typically more compact than [dendrograms](#cluster).
361 |
362 | # d3.tree() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/tree.js), [Examples](https://observablehq.com/@d3/tidy-tree)
363 |
364 | Creates a new tree layout with default settings.
365 |
366 | # tree(root)
367 |
368 | Lays out the specified *root* [hierarchy](#hierarchy), assigning the following properties on *root* and its descendants:
369 |
370 | * *node*.x - the *x*-coordinate of the node
371 | * *node*.y - the *y*-coordinate of the node
372 |
373 | The coordinates *x* and *y* represent an arbitrary coordinate system; for example, you can treat *x* as an angle and *y* as a radius to produce a [radial layout](https://observablehq.com/@d3/radial-tidy-tree). You may want to call [*root*.sort](#node_sort) before passing the hierarchy to the tree layout.
374 |
375 | # tree.size([size])
376 |
377 | If *size* is specified, sets this tree layout’s size to the specified two-element array of numbers [*width*, *height*] and returns this tree layout. If *size* is not specified, returns the current layout size, which defaults to [1, 1]. A layout size of null indicates that a [node size](#tree_nodeSize) will be used instead. The coordinates *x* and *y* represent an arbitrary coordinate system; for example, to produce a [radial layout](https://observablehq.com/@d3/radial-tidy-tree), a size of [360, *radius*] corresponds to a breadth of 360° and a depth of *radius*.
378 |
379 | # tree.nodeSize([size])
380 |
381 | If *size* is specified, sets this tree layout’s node size to the specified two-element array of numbers [*width*, *height*] and returns this tree layout. If *size* is not specified, returns the current node size, which defaults to null. A node size of null indicates that a [layout size](#tree_size) will be used instead. When a node size is specified, the root node is always positioned at ⟨0, 0⟩.
382 |
383 | # tree.separation([separation])
384 |
385 | If *separation* is specified, sets the separation accessor to the specified function and returns this tree layout. If *separation* is not specified, returns the current separation accessor, which defaults to:
386 |
387 | ```js
388 | function separation(a, b) {
389 | return a.parent == b.parent ? 1 : 2;
390 | }
391 | ```
392 |
393 | A variation that is more appropriate for radial layouts reduces the separation gap proportionally to the radius:
394 |
395 | ```js
396 | function separation(a, b) {
397 | return (a.parent == b.parent ? 1 : 2) / a.depth;
398 | }
399 | ```
400 |
401 | The separation accessor is used to separate neighboring nodes. The separation function is passed two nodes *a* and *b*, and must return the desired separation. The nodes are typically siblings, though the nodes may be more distantly related if the layout decides to place such nodes adjacent.
402 |
403 | ### Treemap
404 |
405 | [
](https://observablehq.com/@d3/treemap)
406 |
407 | Introduced by [Ben Shneiderman](http://www.cs.umd.edu/hcil/treemap-history/) in 1991, a **treemap** recursively subdivides area into rectangles according to each node’s associated value. D3’s treemap implementation supports an extensible [tiling method](#treemap_tile): the default [squarified](#treemapSquarify) method seeks to generate rectangles with a [golden](https://en.wikipedia.org/wiki/Golden_ratio) aspect ratio; this offers better readability and size estimation than [slice-and-dice](#treemapSliceDice), which simply alternates between horizontal and vertical subdivision by depth.
408 |
409 | # d3.treemap() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/index.js), [Examples](https://observablehq.com/@d3/treemap)
410 |
411 | Creates a new treemap layout with default settings.
412 |
413 | # treemap(root)
414 |
415 | Lays out the specified *root* [hierarchy](#hierarchy), assigning the following properties on *root* and its descendants:
416 |
417 | * *node*.x0 - the left edge of the rectangle
418 | * *node*.y0 - the top edge of the rectangle
419 | * *node*.x1 - the right edge of the rectangle
420 | * *node*.y1 - the bottom edge of the rectangle
421 |
422 | You must call [*root*.sum](#node_sum) before passing the hierarchy to the treemap layout. You probably also want to call [*root*.sort](#node_sort) to order the hierarchy before computing the layout.
423 |
424 | # treemap.tile([tile])
425 |
426 | If *tile* is specified, sets the [tiling method](#treemap-tiling) to the specified function and returns this treemap layout. If *tile* is not specified, returns the current tiling method, which defaults to [d3.treemapSquarify](#treemapSquarify) with the golden ratio.
427 |
428 | # treemap.size([size])
429 |
430 | If *size* is specified, sets this treemap layout’s size to the specified two-element array of numbers [*width*, *height*] and returns this treemap layout. If *size* is not specified, returns the current size, which defaults to [1, 1].
431 |
432 | # treemap.round([round])
433 |
434 | If *round* is specified, enables or disables rounding according to the given boolean and returns this treemap layout. If *round* is not specified, returns the current rounding state, which defaults to false.
435 |
436 | # treemap.padding([padding])
437 |
438 | If *padding* is specified, sets the [inner](#treemap_paddingInner) and [outer](#treemap_paddingOuter) padding to the specified number or function and returns this treemap layout. If *padding* is not specified, returns the current inner padding function.
439 |
440 | # treemap.paddingInner([padding])
441 |
442 | If *padding* is specified, sets the inner padding to the specified number or function and returns this treemap layout. If *padding* is not specified, returns the current inner padding function, which defaults to the constant zero. If *padding* is a function, it is invoked for each node with children, being passed the current node. The inner padding is used to separate a node’s adjacent children.
443 |
444 | # treemap.paddingOuter([padding])
445 |
446 | If *padding* is specified, sets the [top](#treemap_paddingTop), [right](#treemap_paddingRight), [bottom](#treemap_paddingBottom) and [left](#treemap_paddingLeft) padding to the specified number or function and returns this treemap layout. If *padding* is not specified, returns the current top padding function.
447 |
448 | # treemap.paddingTop([padding])
449 |
450 | If *padding* is specified, sets the top padding to the specified number or function and returns this treemap layout. If *padding* is not specified, returns the current top padding function, which defaults to the constant zero. If *padding* is a function, it is invoked for each node with children, being passed the current node. The top padding is used to separate the top edge of a node from its children.
451 |
452 | # treemap.paddingRight([padding])
453 |
454 | If *padding* is specified, sets the right padding to the specified number or function and returns this treemap layout. If *padding* is not specified, returns the current right padding function, which defaults to the constant zero. If *padding* is a function, it is invoked for each node with children, being passed the current node. The right padding is used to separate the right edge of a node from its children.
455 |
456 | # treemap.paddingBottom([padding])
457 |
458 | If *padding* is specified, sets the bottom padding to the specified number or function and returns this treemap layout. If *padding* is not specified, returns the current bottom padding function, which defaults to the constant zero. If *padding* is a function, it is invoked for each node with children, being passed the current node. The bottom padding is used to separate the bottom edge of a node from its children.
459 |
460 | # treemap.paddingLeft([padding])
461 |
462 | If *padding* is specified, sets the left padding to the specified number or function and returns this treemap layout. If *padding* is not specified, returns the current left padding function, which defaults to the constant zero. If *padding* is a function, it is invoked for each node with children, being passed the current node. The left padding is used to separate the left edge of a node from its children.
463 |
464 | #### Treemap Tiling
465 |
466 | Several built-in tiling methods are provided for use with [*treemap*.tile](#treemap_tile).
467 |
468 | # d3.treemapBinary(node, x0, y0, x1, y1) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/binary.js), [Examples](https://observablehq.com/@d3/treemap)
469 |
470 | Recursively partitions the specified *nodes* into an approximately-balanced binary tree, choosing horizontal partitioning for wide rectangles and vertical partitioning for tall rectangles.
471 |
472 | # d3.treemapDice(node, x0, y0, x1, y1) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/dice.js), [Examples](https://observablehq.com/@d3/treemap)
473 |
474 | Divides the rectangular area specified by *x0*, *y0*, *x1*, *y1* horizontally according the value of each of the specified *node*’s children. The children are positioned in order, starting with the left edge (*x0*) of the given rectangle. If the sum of the children’s values is less than the specified *node*’s value (*i.e.*, if the specified *node* has a non-zero internal value), the remaining empty space will be positioned on the right edge (*x1*) of the given rectangle.
475 |
476 | # d3.treemapSlice(node, x0, y0, x1, y1) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/slice.js), [Examples](https://observablehq.com/@d3/treemap)
477 |
478 | Divides the rectangular area specified by *x0*, *y0*, *x1*, *y1* vertically according the value of each of the specified *node*’s children. The children are positioned in order, starting with the top edge (*y0*) of the given rectangle. If the sum of the children’s values is less than the specified *node*’s value (*i.e.*, if the specified *node* has a non-zero internal value), the remaining empty space will be positioned on the bottom edge (*y1*) of the given rectangle.
479 |
480 | # d3.treemapSliceDice(node, x0, y0, x1, y1) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/sliceDice.js), [Examples](https://observablehq.com/@d3/treemap)
481 |
482 | If the specified *node* has odd depth, delegates to [treemapSlice](#treemapSlice); otherwise delegates to [treemapDice](#treemapDice).
483 |
484 | # d3.treemapSquarify(node, x0, y0, x1, y1) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/squarify.js), [Examples](https://observablehq.com/@d3/treemap)
485 |
486 | Implements the [squarified treemap](https://www.win.tue.nl/~vanwijk/stm.pdf) algorithm by Bruls *et al.*, which seeks to produce rectangles of a given [aspect ratio](#squarify_ratio).
487 |
488 | # d3.treemapResquarify(node, x0, y0, x1, y1) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/resquarify.js), [Examples](https://observablehq.com/@d3/animated-treemap)
489 |
490 | Like [d3.treemapSquarify](#treemapSquarify), except preserves the topology (node adjacencies) of the previous layout computed by d3.treemapResquarify, if there is one and it used the same [target aspect ratio](#squarify_ratio). This tiling method is good for animating changes to treemaps because it only changes node sizes and not their relative positions, thus avoiding distracting shuffling and occlusion. The downside of a stable update, however, is a suboptimal layout for subsequent updates: only the first layout uses the Bruls *et al.* squarified algorithm.
491 |
492 | # squarify.ratio(ratio) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/treemap/squarify.js), [Examples](https://observablehq.com/@d3/treemap)
493 |
494 | Specifies the desired aspect ratio of the generated rectangles. The *ratio* must be specified as a number greater than or equal to one. Note that the orientation of the generated rectangles (tall or wide) is not implied by the ratio; for example, a ratio of two will attempt to produce a mixture of rectangles whose *width*:*height* ratio is either 2:1 or 1:2. (However, you can approximately achieve this result by generating a square treemap at different dimensions, and then [stretching the treemap](https://observablehq.com/@d3/stretched-treemap) to the desired aspect ratio.) Furthermore, the specified *ratio* is merely a hint to the tiling algorithm; the rectangles are not guaranteed to have the specified aspect ratio. If not specified, the aspect ratio defaults to the golden ratio, φ = (1 + sqrt(5)) / 2, per [Kong *et al.*](http://vis.stanford.edu/papers/perception-treemaps)
495 |
496 | ### Partition
497 |
498 | [
](https://observablehq.com/@d3/icicle)
499 |
500 | The **partition layout** produces adjacency diagrams: a space-filling variant of a node-link tree diagram. Rather than drawing a link between parent and child in the hierarchy, nodes are drawn as solid areas (either arcs or rectangles), and their placement relative to other nodes reveals their position in the hierarchy. The size of the nodes encodes a quantitative dimension that would be difficult to show in a node-link diagram.
501 |
502 | # d3.partition() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/partition.js), [Examples](https://observablehq.com/@d3/icicle)
503 |
504 | Creates a new partition layout with the default settings.
505 |
506 | # partition(root)
507 |
508 | Lays out the specified *root* [hierarchy](#hierarchy), assigning the following properties on *root* and its descendants:
509 |
510 | * *node*.x0 - the left edge of the rectangle
511 | * *node*.y0 - the top edge of the rectangle
512 | * *node*.x1 - the right edge of the rectangle
513 | * *node*.y1 - the bottom edge of the rectangle
514 |
515 | You must call [*root*.sum](#node_sum) before passing the hierarchy to the partition layout. You probably also want to call [*root*.sort](#node_sort) to order the hierarchy before computing the layout.
516 |
517 | # partition.size([size])
518 |
519 | If *size* is specified, sets this partition layout’s size to the specified two-element array of numbers [*width*, *height*] and returns this partition layout. If *size* is not specified, returns the current size, which defaults to [1, 1].
520 |
521 | # partition.round([round])
522 |
523 | If *round* is specified, enables or disables rounding according to the given boolean and returns this partition layout. If *round* is not specified, returns the current rounding state, which defaults to false.
524 |
525 | # partition.padding([padding])
526 |
527 | If *padding* is specified, sets the padding to the specified number and returns this partition layout. If *padding* is not specified, returns the current padding, which defaults to zero. The padding is used to separate a node’s adjacent children.
528 |
529 | ### Pack
530 |
531 | [
](https://observablehq.com/@d3/circle-packing)
532 |
533 | Enclosure diagrams use containment (nesting) to represent a hierarchy. The size of the leaf circles encodes a quantitative dimension of the data. The enclosing circles show the approximate cumulative size of each subtree, but due to wasted space there is some distortion; only the leaf nodes can be compared accurately. Although [circle packing](http://en.wikipedia.org/wiki/Circle_packing) does not use space as efficiently as a [treemap](#treemap), the “wasted” space more prominently reveals the hierarchical structure.
534 |
535 | # d3.pack() · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/pack/index.js), [Examples](https://observablehq.com/@d3/circle-packing)
536 |
537 | Creates a new pack layout with the default settings.
538 |
539 | # pack(root)
540 |
541 | Lays out the specified *root* [hierarchy](#hierarchy), assigning the following properties on *root* and its descendants:
542 |
543 | * *node*.x - the *x*-coordinate of the circle’s center
544 | * *node*.y - the *y*-coordinate of the circle’s center
545 | * *node*.r - the radius of the circle
546 |
547 | You must call [*root*.sum](#node_sum) before passing the hierarchy to the pack layout. You probably also want to call [*root*.sort](#node_sort) to order the hierarchy before computing the layout.
548 |
549 | # pack.radius([radius])
550 |
551 | If *radius* is specified, sets the pack layout’s radius accessor to the specified function and returns this pack layout. If *radius* is not specified, returns the current radius accessor, which defaults to null. If the radius accessor is null, the radius of each leaf circle is derived from the leaf *node*.value (computed by [*node*.sum](#node_sum)); the radii are then scaled proportionally to fit the [layout size](#pack_size). If the radius accessor is not null, the radius of each leaf circle is specified exactly by the function.
552 |
553 | # pack.size([size])
554 |
555 | If *size* is specified, sets this pack layout’s size to the specified two-element array of numbers [*width*, *height*] and returns this pack layout. If *size* is not specified, returns the current size, which defaults to [1, 1].
556 |
557 | # pack.padding([padding])
558 |
559 | If *padding* is specified, sets this pack layout’s padding accessor to the specified number or function and returns this pack layout. If *padding* is not specified, returns the current padding accessor, which defaults to the constant zero. When siblings are packed, tangent siblings will be separated by approximately the specified padding; the enclosing parent circle will also be separated from its children by approximately the specified padding. If an [explicit radius](#pack_radius) is not specified, the padding is approximate because a two-pass algorithm is needed to fit within the [layout size](#pack_size): the circles are first packed without padding; a scaling factor is computed and applied to the specified padding; and lastly the circles are re-packed with padding.
560 |
561 | # d3.packSiblings(circles) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/pack/siblings.js)
562 |
563 | Packs the specified array of *circles*, each of which must have a *circle*.r property specifying the circle’s radius. Assigns the following properties to each circle:
564 |
565 | * *circle*.x - the *x*-coordinate of the circle’s center
566 | * *circle*.y - the *y*-coordinate of the circle’s center
567 |
568 | The circles are positioned according to the front-chain packing algorithm by [Wang *et al.*](https://dl.acm.org/citation.cfm?id=1124851)
569 |
570 | # d3.packEnclose(circles) · [Source](https://github.com/d3/d3-hierarchy/blob/master/src/pack/enclose.js), [Examples](https://observablehq.com/@d3/d3-packenclose)
571 |
572 | Computes the [smallest circle](https://en.wikipedia.org/wiki/Smallest-circle_problem) that encloses the specified array of *circles*, each of which must have a *circle*.r property specifying the circle’s radius, and *circle*.x and *circle*.y properties specifying the circle’s center. The enclosing circle is computed using the [Matoušek-Sharir-Welzl algorithm](http://www.inf.ethz.ch/personal/emo/PublFiles/SubexLinProg_ALG16_96.pdf). (See also [Apollonius’ Problem](https://bl.ocks.org/mbostock/751fdd637f4bc2e3f08b).)
573 |
--------------------------------------------------------------------------------