UNPKG

@antv/g6

Version:

graph visualization frame work

209 lines (170 loc) 5.99 kB
/** * @fileOverview concentric layout * @author shiwu.wyy@antfin.com * this algorithm refers to <cytoscape.js> - https://github.com/cytoscape/cytoscape.js/ */ var Layout = require('./layout'); var isString = require('@antv/util/lib/type/is-string'); function getDegree(n, nodeIdxMap, edges) { var degrees = []; for (var i = 0; i < n; i++) { degrees[i] = 0; } edges.forEach(function (e) { degrees[nodeIdxMap.get(e.source)] += 1; degrees[nodeIdxMap.get(e.target)] += 1; }); return degrees; } /** * 同心圆布局 */ Layout.registerLayout('concentric', { getDefaultCfg: function getDefaultCfg() { return { center: [0, 0], // 布局中心 nodeSize: 30, minNodeSpacing: 10, // min spacing between outside of nodes (used for radius adjustment) preventOverlap: false, // prevents node overlap, may overflow boundingBox if not enough space sweep: undefined, // how many radians should be between the first and last node (defaults to full circle) equidistant: false, // whether levels have an equal radial distance betwen them, may cause bounding box overflow startAngle: 3 / 2 * Math.PI, // where nodes start in radians clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) maxLevelDiff: undefined, // the letiation of concentric values in each level sortBy: 'degree' // 根据 sortBy 指定的属性进行排布,数值高的放在中心。如果是 sortBy 则会计算节点度数,度数最高的放在中心。 }; }, /** * 执行布局 */ execute: function execute() { var self = this; var nodes = self.nodes; var edges = self.edges; var n = nodes.length; var center = self.center; if (n === 0) { return; } else if (n === 1) { nodes[0].x = center[0]; nodes[0].y = center[1]; return; } var layoutNodes = []; var maxNodeSize; if (isNaN(self.nodeSize)) { maxNodeSize = Math.max(self.nodeSize[0], self.nodeSize[1]); } else { maxNodeSize = self.nodeSize; } nodes.forEach(function (node) { layoutNodes.push(node); var nodeSize; if (isNaN(node.size)) { nodeSize = Math.max(node.size[0], node.size[1]); } else { nodeSize = node.size; } maxNodeSize = Math.max(maxNodeSize, nodeSize); }); var width = self.width; if (!width && typeof window !== 'undefined') { width = window.innerWidth; } var height = self.height; if (!height && typeof height !== 'undefined') { height = window.innerHeight; } self.clockwise = self.counterclockwise !== undefined ? !self.counterclockwise : self.clockwise; // layout var nodeMap = new Map(); var nodeIdxMap = new Map(); layoutNodes.forEach(function (node, i) { nodeMap.set(node.id, node); nodeIdxMap.set(node.id, i); }); self.nodeMap = nodeMap; // get the node degrees if (self.sortBy === 'degree' || !isString(self.sortBy) || layoutNodes[0][self.sortBy] === undefined) { self.sortBy = 'degree'; if (isNaN(nodes[0].degree)) { var values = getDegree(nodes.length, nodeIdxMap, edges); layoutNodes.forEach(function (node, i) { node.degree = values[i]; }); } } // sort nodes by value layoutNodes.sort(function (n1, n2) { return n2[self.sortBy] - n1[self.sortBy]; }); self.maxValueNode = layoutNodes[0]; self.maxLevelDiff = self.maxLevelDiff || self.maxValueNode[self.sortBy] / 4; // 0.5; // put the values into levels var levels = [[]]; var currentLevel = levels[0]; layoutNodes.forEach(function (node) { if (currentLevel.length > 0) { var diff = Math.abs(currentLevel[0][self.sortBy] - node[self.sortBy]); if (diff >= self.maxLevelDiff) { currentLevel = []; levels.push(currentLevel); } } currentLevel.push(node); }); // create positions for levels var minDist = maxNodeSize + self.minNodeSpacing; // min dist between nodes if (!self.preventOverlap) { // then strictly constrain to bb var firstLvlHasMulti = levels.length > 0 && levels[0].length > 1; var maxR = Math.min(self.width, self.height) / 2 - minDist; var rStep = maxR / (levels.length + firstLvlHasMulti ? 1 : 0); minDist = Math.min(minDist, rStep); } // find the metrics for each level var r = 0; levels.forEach(function (level) { var sweep = self.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / level.length : self.sweep; var dTheta = level.dTheta = sweep / Math.max(1, level.length - 1); // calculate the radius if (level.length > 1 && self.preventOverlap) { // but only if more than one node (can't overlap) var dcos = Math.cos(dTheta) - Math.cos(0); var dsin = Math.sin(dTheta) - Math.sin(0); var rMin = Math.sqrt(minDist * minDist / (dcos * dcos + dsin * dsin)); // s.t. no nodes overlapping r = Math.max(rMin, r); } level.r = r; r += minDist; }); if (self.equidistant) { var rDeltaMax = 0; var _r = 0; for (var i = 0; i < levels.length; i++) { var level = levels[i]; var rDelta = level.r - _r; rDeltaMax = Math.max(rDeltaMax, rDelta); } _r = 0; levels.forEach(function (level, i) { if (i === 0) { _r = level.r; } level.r = _r; _r += rDeltaMax; }); } // calculate the node positions levels.forEach(function (level) { var dTheta = level.dTheta; var r = level.r; level.forEach(function (node, j) { var theta = self.startAngle + (self.clockwise ? 1 : -1) * dTheta * j; node.x = center[0] + r * Math.cos(theta); node.y = center[1] + r * Math.sin(theta); }); }); } });