UNPKG

@antv/g6

Version:

graph visualization frame work

231 lines (219 loc) 6.77 kB
/** * @fileOverview random layout * @author shiwu.wyy@antfin.com */ const Layout = require('./layout'); function getDegree(n, nodeMap, edges) { const degrees = []; for (let i = 0; i < n; i++) { degrees[i] = 0; } edges.forEach(e => { degrees[nodeMap.get(e.source)] += 1; degrees[nodeMap.get(e.target)] += 1; }); return degrees; } function initHierarchy(nodes, edges, nodeMap, directed) { nodes.forEach((n, i) => { nodes[i].children = []; nodes[i].parent = []; }); if (directed) { edges.forEach(e => { const sourceIdx = nodeMap.get(e.source); const targetIdx = nodeMap.get(e.target); nodes[sourceIdx].children.push(nodes[targetIdx]); nodes[targetIdx].parent.push(nodes[sourceIdx]); }); } else { edges.forEach(e => { const sourceIdx = nodeMap.get(e.source); const targetIdx = nodeMap.get(e.target); nodes[sourceIdx].children.push(nodes[targetIdx]); nodes[targetIdx].children.push(nodes[sourceIdx]); }); } } function connect(a, b, edges) { const m = edges.length; for (let i = 0; i < m; i++) { if ((a.id === edges[i].source && b.id === edges[i].target) || (b.id === edges[i].source && a.id === edges[i].target)) { return true; } } return false; } function compareDegree(a, b) { if (a.degree < b.degree) { return -1; } if (a.degree > b.degree) { return 1; } return 0; } /** * 圆形布局 */ Layout.registerLayout('circular', { getDefaultCfg() { return { center: [ 0, 0 ], // 布局中心 radius: null, // 默认固定半径,若设置了 radius,则 startRadius 与 endRadius 不起效 startRadius: null, // 默认起始半径 endRadius: null, // 默认终止半径 startAngle: 0, // 默认起始角度 endAngle: 2 * Math.PI, // 默认终止角度 clockwise: true, // 是否顺时针 divisions: 1, // 节点在环上分成段数(几个段将均匀分布),在 endRadius - startRadius != 0 时生效 ordering: null, // 节点在环上排序的依据。可选: 'topology', 'degree', 'null' angleRatio: 1 // how many 2*pi from first to last nodes }; }, /** * 执行布局 */ execute() { const self = this; const nodes = self.nodes; const edges = self.edges; const n = nodes.length; const center = self.center; if (n === 0) { return; } else if (n === 1) { nodes[0].x = center[0]; nodes[0].y = center[1]; return; } let radius = self.radius; let startRadius = self.startRadius; let endRadius = self.endRadius; const divisions = self.divisions; const startAngle = self.startAngle; const endAngle = self.endAngle; const angleStep = (endAngle - startAngle) / n; // layout const nodeMap = new Map(); nodes.forEach((node, i) => { nodeMap.set(node.id, i); }); self.nodeMap = nodeMap; const degrees = getDegree(nodes.length, nodeMap, edges); self.degrees = degrees; let width = self.width; if (!width && typeof window !== 'undefined') { width = window.innerWidth; } let height = self.height; if (!height && typeof height !== 'undefined') { height = window.innerHeight; } if (!radius && !startRadius && !endRadius) { radius = height > width ? width / 2 : height / 2; } else if (!startRadius && endRadius) { startRadius = endRadius; } else if (startRadius && !endRadius) { endRadius = startRadius; } const angleRatio = self.angleRatio; const astep = angleStep * angleRatio; self.astep = astep; const ordering = self.ordering; let layoutNodes = []; if (ordering === 'topology') { // layout according to the topology layoutNodes = self.topologyOrdering(); } else if (ordering === 'degree') { // layout according to the descent order of degrees layoutNodes = self.degreeOrdering(); } else { // layout according to the original order in the data.nodes layoutNodes = nodes; } const clockwise = self.clockwise; const divN = Math.ceil(n / divisions); // node number in each division for (let i = 0; i < n; ++i) { let r = radius; if (!r) { r = startRadius + (i * (endRadius - startRadius)) / (n - 1); } let angle = startAngle + (i % divN) * astep + ((2 * Math.PI) / divisions) * Math.floor(i / divN); if (!clockwise) { angle = endAngle - (i % divN) * astep - ((2 * Math.PI) / divisions) * Math.floor(i / divN); } layoutNodes[i].x = center[0] + Math.cos(angle) * r; layoutNodes[i].y = center[1] + Math.sin(angle) * r; layoutNodes[i].weight = degrees[i]; } }, /** * 根据节点的拓扑结构排序 * @return {array} orderedNodes 排序后的结果 */ topologyOrdering() { const self = this; const degrees = self.degrees; const edges = self.edges; const nodes = self.nodes; const nodeMap = self.nodeMap; const orderedNodes = [ nodes[0] ]; const pickFlags = []; const n = nodes.length; pickFlags[0] = true; initHierarchy(nodes, edges, nodeMap, false); let k = 0; nodes.forEach((node, i) => { if (i === 0) return; else if ((i === n - 1 || degrees[i] !== degrees[i + 1] || connect(orderedNodes[k], node, edges)) && pickFlags[i] !== true) { orderedNodes.push(node); pickFlags[i] = true; k++; } else { const children = orderedNodes[k].children; let foundChild = false; for (let j = 0; j < children.length; ++j) { const childIdx = nodeMap.get(children[j].id); if (degrees[childIdx] === degrees[i] && pickFlags[childIdx] !== true) { orderedNodes.push(nodes[childIdx]); pickFlags[childIdx] = true; foundChild = true; break; } } let ii = 0; while (!foundChild) { if (!pickFlags[ii]) { orderedNodes.push(nodes[ii]); pickFlags[ii] = true; foundChild = true; } ii++; if (ii === n) break; } } }); return orderedNodes; }, /** * 根据节点度数大小排序 * @return {array} orderedNodes 排序后的结果 */ degreeOrdering() { const self = this; const nodes = self.nodes; const orderedNodes = []; const degrees = self.degrees; nodes.forEach((node, i) => { node.degree = degrees[i]; orderedNodes.push(node); }); orderedNodes.sort(compareDegree); return orderedNodes; } });