├── 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 | [Stratify](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 | [Dendrogram](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 | [Tidy Tree](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 | [Treemap](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 | [Partition](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 | [Circle-Packing](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 | --------------------------------------------------------------------------------