UNPKG

@antv/layout

Version:
266 lines (263 loc) 8.1 kB
import { DagreGraph } from './graph.js'; import isNumber from '../../node_modules/@antv/util/esm/lodash/is-number.js'; const safeSort = (valueA, valueB) => { return Number(valueA) - Number(valueB); }; /* * Adds a dummy node to the graph and return v. */ const addDummyNode = (g, type, data, name) => { let v; do { v = `${name}${Math.random()}`; } while (g.hasNode(v)); data.dummy = type; g.addNode({ id: v, data, }); return v; }; /* * Returns a new graph with only simple edges. Handles aggregation of data * associated with multi-edges. */ const simplify = (g) => { const simplified = new DagreGraph(); g.getAllNodes().forEach((v) => { simplified.addNode(Object.assign({}, v)); }); g.getAllEdges().forEach((e) => { const edge = simplified .getRelatedEdges(e.source, 'out') .find((edge) => edge.target === e.target); if (!edge) { simplified.addEdge({ id: e.id, source: e.source, target: e.target, data: { weight: e.data.weight || 0, minlen: e.data.minlen || 1, }, }); } else { simplified.updateEdgeData(edge === null || edge === void 0 ? void 0 : edge.id, Object.assign(Object.assign({}, edge.data), { weight: edge.data.weight + e.data.weight || 0, minlen: Math.max(edge.data.minlen, e.data.minlen || 1) })); } }); return simplified; }; const asNonCompoundGraph = (g) => { const simplified = new DagreGraph(); g.getAllNodes().forEach((node) => { if (!g.getChildren(node.id).length) { simplified.addNode(Object.assign({}, node)); } }); g.getAllEdges().forEach((edge) => { simplified.addEdge(edge); }); return simplified; }; const zipObject = (keys, values) => { return keys === null || keys === void 0 ? void 0 : keys.reduce((obj, key, i) => { obj[key] = values[i]; return obj; }, {}); }; /* * Finds where a line starting at point ({x, y}) would intersect a rectangle * ({x, y, width, height}) if it were pointing at the rectangle's center. */ const intersectRect = (rect, point) => { const x = Number(rect.x); const y = Number(rect.y); // Rectangle intersection algorithm from: // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes const dx = Number(point.x) - x; const dy = Number(point.y) - y; let w = Number(rect.width) / 2; let h = Number(rect.height) / 2; if (!dx && !dy) { // completely overlapped directly, then return points its self return { x: 0, y: 0 }; } let sx; let sy; if (Math.abs(dy) * w > Math.abs(dx) * h) { // Intersection is top or bottom of rect. if (dy < 0) { h = -h; } sx = (h * dx) / dy; sy = h; } else { // Intersection is left or right of rect. if (dx < 0) { w = -w; } sx = w; sy = (w * dy) / dx; } return { x: x + sx, y: y + sy }; }; /* * Given a DAG with each node assigned "rank" and "order" properties, this * const will produce a matrix with the ids of each node. */ const buildLayerMatrix = (g) => { const layeringNodes = []; const rankMax = maxRank(g) + 1; for (let i = 0; i < rankMax; i++) { layeringNodes.push([]); } // const layering = _.map(_.range(maxRank(g) + 1), function() { return []; }); g.getAllNodes().forEach((node) => { const rank = node.data.rank; if (rank !== undefined && layeringNodes[rank]) { layeringNodes[rank].push(node.id); } }); for (let i = 0; i < rankMax; i++) { layeringNodes[i] = layeringNodes[i].sort((va, vb) => safeSort(g.getNode(va).data.order, g.getNode(vb).data.order)); } return layeringNodes; }; /* * Adjusts the ranks for all nodes in the graph such that all nodes v have * rank(v) >= 0 and at least one node w has rank(w) = 0. */ const normalizeRanks = (g) => { const nodeRanks = g .getAllNodes() .filter((v) => v.data.rank !== undefined) .map((v) => v.data.rank); const min = Math.min(...nodeRanks); g.getAllNodes().forEach((v) => { if (v.data.hasOwnProperty('rank') && min !== Infinity) { v.data.rank -= min; } }); }; const removeEmptyRanks = (g, nodeRankFactor = 0) => { // Ranks may not start at 0, so we need to offset them const nodes = g.getAllNodes(); const nodeRanks = nodes .filter((v) => v.data.rank !== undefined) .map((v) => v.data.rank); const offset = Math.min(...nodeRanks); const layers = []; nodes.forEach((v) => { const rank = (v.data.rank || 0) - offset; if (!layers[rank]) { layers[rank] = []; } layers[rank].push(v.id); }); let delta = 0; for (let i = 0; i < layers.length; i++) { const vs = layers[i]; if (vs === undefined) { if (i % nodeRankFactor !== 0) { delta -= 1; } } else if (delta) { vs === null || vs === void 0 ? void 0 : vs.forEach((v) => { const node = g.getNode(v); if (node) { node.data.rank = node.data.rank || 0; node.data.rank += delta; } }); } } }; const addBorderNode$1 = (g, prefix, rank, order) => { const node = { width: 0, height: 0, }; if (isNumber(rank) && isNumber(order)) { node.rank = rank; node.order = order; } return addDummyNode(g, 'border', node, prefix); }; const maxRank = (g) => { let maxRank; g.getAllNodes().forEach((v) => { const rank = v.data.rank; if (rank !== undefined) { if (maxRank === undefined || rank > maxRank) { maxRank = rank; } } }); if (!maxRank) { maxRank = 0; } return maxRank; }; /* * Partition a collection into two groups: `lhs` and `rhs`. If the supplied * const returns true for an entry it goes into `lhs`. Otherwise it goes * into `rhs. */ const partition = (collection, fn) => { const result = { lhs: [], rhs: [] }; collection === null || collection === void 0 ? void 0 : collection.forEach((value) => { if (fn(value)) { result.lhs.push(value); } else { result.rhs.push(value); } }); return result; }; const minBy = (array, func) => { return array.reduce((a, b) => { const valA = func(a); const valB = func(b); return valA > valB ? b : a; }); }; const doDFS = (graph, node, postorder, visited, navigator, result) => { if (!visited.includes(node.id)) { visited.push(node.id); if (!postorder) { result.push(node.id); } const neighbors = navigator(node.id); if (neighbors) { neighbors.forEach((n) => doDFS(graph, n, postorder, visited, navigator, result)); } if (postorder) { result.push(node.id); } } }; /** * @description DFS traversal. * @description.zh-CN DFS 遍历。 */ const dfs$1 = (graph, node, order, isDirected) => { const nodes = Array.isArray(node) ? node : [node]; const navigator = (n) => graph.getNeighbors(n); const results = []; const visited = []; nodes.forEach((node) => { if (!graph.hasNode(node.id)) { throw new Error(`Graph does not have node: ${node}`); } else { doDFS(graph, node, order === 'post', visited, navigator, results); } }); return results; }; export { addBorderNode$1 as addBorderNode, addDummyNode, asNonCompoundGraph, buildLayerMatrix, dfs$1 as dfs, intersectRect, maxRank, minBy, normalizeRanks, partition, removeEmptyRanks, simplify, zipObject }; //# sourceMappingURL=util.js.map