|
import {optional} from "./accessors.js"; |
|
import {Node, computeHeight} from "./hierarchy/index.js"; |
|
|
|
var preroot = {depth: -1}, |
|
ambiguous = {}, |
|
imputed = {}; |
|
|
|
function defaultId(d) { |
|
return d.id; |
|
} |
|
|
|
function defaultParentId(d) { |
|
return d.parentId; |
|
} |
|
|
|
export default function() { |
|
var id = defaultId, |
|
parentId = defaultParentId, |
|
path; |
|
|
|
function stratify(data) { |
|
var nodes = Array.from(data), |
|
currentId = id, |
|
currentParentId = parentId, |
|
n, |
|
d, |
|
i, |
|
root, |
|
parent, |
|
node, |
|
nodeId, |
|
nodeKey, |
|
nodeByKey = new Map; |
|
|
|
if (path != null) { |
|
const I = nodes.map((d, i) => normalize(path(d, i, data))); |
|
const P = I.map(parentof); |
|
const S = new Set(I).add(""); |
|
for (const i of P) { |
|
if (!S.has(i)) { |
|
S.add(i); |
|
I.push(i); |
|
P.push(parentof(i)); |
|
nodes.push(imputed); |
|
} |
|
} |
|
currentId = (_, i) => I[i]; |
|
currentParentId = (_, i) => P[i]; |
|
} |
|
|
|
for (i = 0, n = nodes.length; i < n; ++i) { |
|
d = nodes[i], node = nodes[i] = new Node(d); |
|
if ((nodeId = currentId(d, i, data)) != null && (nodeId += "")) { |
|
nodeKey = node.id = nodeId; |
|
nodeByKey.set(nodeKey, nodeByKey.has(nodeKey) ? ambiguous : node); |
|
} |
|
if ((nodeId = currentParentId(d, i, data)) != null && (nodeId += "")) { |
|
node.parent = nodeId; |
|
} |
|
} |
|
|
|
for (i = 0; i < n; ++i) { |
|
node = nodes[i]; |
|
if (nodeId = node.parent) { |
|
parent = nodeByKey.get(nodeId); |
|
if (!parent) throw new Error("missing: " + nodeId); |
|
if (parent === ambiguous) throw new Error("ambiguous: " + nodeId); |
|
if (parent.children) parent.children.push(node); |
|
else parent.children = [node]; |
|
node.parent = parent; |
|
} else { |
|
if (root) throw new Error("multiple roots"); |
|
root = node; |
|
} |
|
} |
|
|
|
if (!root) throw new Error("no root"); |
|
|
|
|
|
|
|
if (path != null) { |
|
while (root.data === imputed && root.children.length === 1) { |
|
root = root.children[0], --n; |
|
} |
|
for (let i = nodes.length - 1; i >= 0; --i) { |
|
node = nodes[i]; |
|
if (node.data !== imputed) break; |
|
node.data = null; |
|
} |
|
} |
|
|
|
root.parent = preroot; |
|
root.eachBefore(function(node) { node.depth = node.parent.depth + 1; --n; }).eachBefore(computeHeight); |
|
root.parent = null; |
|
if (n > 0) throw new Error("cycle"); |
|
|
|
return root; |
|
} |
|
|
|
stratify.id = function(x) { |
|
return arguments.length ? (id = optional(x), stratify) : id; |
|
}; |
|
|
|
stratify.parentId = function(x) { |
|
return arguments.length ? (parentId = optional(x), stratify) : parentId; |
|
}; |
|
|
|
stratify.path = function(x) { |
|
return arguments.length ? (path = optional(x), stratify) : path; |
|
}; |
|
|
|
return stratify; |
|
} |
|
|
|
|
|
|
|
|
|
function normalize(path) { |
|
path = `${path}`; |
|
let i = path.length; |
|
if (slash(path, i - 1) && !slash(path, i - 2)) path = path.slice(0, -1); |
|
return path[0] === "/" ? path : `/${path}`; |
|
} |
|
|
|
|
|
|
|
|
|
function parentof(path) { |
|
let i = path.length; |
|
if (i < 2) return ""; |
|
while (--i > 1) if (slash(path, i)) break; |
|
return path.slice(0, i); |
|
} |
|
|
|
|
|
|
|
|
|
function slash(path, i) { |
|
if (path[i] === "/") { |
|
let k = 0; |
|
while (i > 0 && path[--i] === "\\") ++k; |
|
if ((k & 1) === 0) return true; |
|
} |
|
return false; |
|
} |
|
|