UNPKG

@antv/g6

Version:

graph visualization frame work

293 lines (261 loc) 8.08 kB
/** * @fileOverview grid layout * @author shiwu.wyy@antfin.com * this algorithm refers to <cytoscape.js> - https://github.com/cytoscape/cytoscape.js/ */ const Layout = require('./layout'); const isString = require('@antv/util/lib/type/is-string'); function getDegree(n, nodeIdxMap, edges) { const degrees = []; for (let i = 0; i < n; i++) { degrees[i] = 0; } edges.forEach(e => { degrees[nodeIdxMap.get(e.source)] += 1; degrees[nodeIdxMap.get(e.target)] += 1; }); return degrees; } /** * 网格布局 */ Layout.registerLayout('grid', { getDefaultCfg() { return { begin: [ 0, 0 ], // 布局起始点 preventOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space preventOverlapPadding: 10, // extra spacing around nodes when preventOverlap: true condense: false, // uses all available space on false, uses minimal space on true rows: undefined, // force num of rows in the grid cols: undefined, // force num of columns in the grid position() {}, // returns { row, col } for element sortBy: 'degree', // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } nodeSize: 30 }; }, /** * 执行布局 */ 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; } const edges = self.edges; const layoutNodes = []; nodes.forEach(node => { layoutNodes.push(node); }); const nodeIdxMap = new Map(); layoutNodes.forEach((node, i) => { nodeIdxMap.set(node.id, i); }); if (self.sortBy === 'degree' || !isString(self.sortBy) || layoutNodes[0][self.sortBy] === undefined) { self.sortBy = 'degree'; if (isNaN(nodes[0].degree)) { const values = getDegree(layoutNodes.length, nodeIdxMap, edges); layoutNodes.forEach((node, i) => { node.degree = values[i]; }); } } // sort nodes by value layoutNodes.sort((n1, n2) => { return n2[self.sortBy] - n1[self.sortBy]; }); let width = self.width; if (!width && typeof window !== 'undefined') { width = window.innerWidth; } let height = self.height; if (!height && typeof height !== 'undefined') { height = window.innerHeight; } // width/height * splits^2 = cells where splits is number of times to split width self.cells = n; self.splits = Math.sqrt(self.cells * self.height / self.width); self.rows = Math.round(self.splits); self.cols = Math.round(self.width / self.height * self.splits); const oRows = self.rows; const oCols = self.cols != null ? self.cols : self.columns; // if rows or columns were set in self, use those values if (oRows != null && oCols != null) { self.rows = oRows; self.cols = oCols; } else if (oRows != null && oCols == null) { self.rows = oRows; self.cols = Math.ceil(self.cells / self.rows); } else if (oRows == null && oCols != null) { self.cols = oCols; self.rows = Math.ceil(self.cells / self.cols); } else if (self.cols * self.rows > self.cells) { // otherwise use the automatic values and adjust accordingly // if rounding was up, see if we can reduce rows or columns const sm = self.small(); const lg = self.large(); // reducing the small side takes away the most cells, so try it first if ((sm - 1) * lg >= self.cells) { self.small(sm - 1); } else if ((lg - 1) * sm >= self.cells) { self.large(lg - 1); } } else { // if rounding was too low, add rows or columns while (self.cols * self.rows < self.cells) { const sm = self.small(); const lg = self.large(); // try to add to larger side first (adds less in multiplication) if ((lg + 1) * sm >= self.cells) { self.large(lg + 1); } else { self.small(sm + 1); } } } self.cellWidth = self.width / self.cols; self.cellHeight = self.height / self.rows; if (self.condense) { self.cellWidth = 0; self.cellHeight = 0; } if (self.preventOverlap) { layoutNodes.forEach(node => { if (node.x == null || node.y == null) { // for bb node.x = 0; node.y = 0; } let nodew; let nodeh; if (isNaN(node.size)) { nodew = node.size[0]; nodeh = node.size[1]; } else { nodew = node.size; nodeh = node.size; } if (isNaN(nodew) || isNaN(nodeh)) { if (isNaN(self.nodeSize)) { nodew = self.nodeSize[0]; nodeh = self.nodeSize[1]; } else { nodew = self.nodeSize; nodeh = self.nodeSize; } } const p = self.preventOverlapPadding; const w = nodew + p; const h = nodeh + p; self.cellWidth = Math.max(self.cellWidth, w); self.cellHeight = Math.max(self.cellHeight, h); }); } self.cellUsed = {}; // e.g. 'c-0-2' => true // to keep track of current cell position self.row = 0; self.col = 0; // get a cache of all the manual positions self.id2manPos = {}; for (let i = 0; i < layoutNodes.length; i++) { const node = layoutNodes[i]; const rcPos = self.position(node); if (rcPos && (rcPos.row !== undefined || rcPos.col !== undefined)) { // must have at least row or col def'd const pos = { row: rcPos.row, col: rcPos.col }; if (pos.col === undefined) { // find unused col pos.col = 0; while (self.used(pos.row, pos.col)) { pos.col++; } } else if (pos.row === undefined) { // find unused row pos.row = 0; while (self.used(pos.row, pos.col)) { pos.row++; } } self.id2manPos[ node.id ] = pos; self.use(pos.row, pos.col); } self.getPos(node); } }, small(val) { const self = this; let res; if (val == null) { res = Math.min(self.rows, self.cols); } else { const min = Math.min(self.rows, self.cols); if (min === self.rows) { self.rows = val; } else { self.cols = val; } } return res; }, large(val) { const self = this; let res; if (val == null) { res = Math.max(self.rows, self.cols); } else { const max = Math.max(self.rows, self.cols); if (max === self.rows) { self.rows = val; } else { self.cols = val; } } return res; }, used(row, col) { const self = this; return self.cellUsed[ 'c-' + row + '-' + col ] || false; }, use(row, col) { const self = this; self.cellUsed[ 'c-' + row + '-' + col ] = true; }, moveToNextCell() { const self = this; self.col++; if (self.col >= self.cols) { self.col = 0; self.row++; } }, getPos(node) { const self = this; const begin = self.begin; const cellWidth = self.cellWidth; const cellHeight = self.cellHeight; let x; let y; // see if we have a manual position set const rcPos = self.id2manPos[ node.id ]; if (rcPos) { x = rcPos.col * cellWidth + cellWidth / 2 + begin[0]; y = rcPos.row * cellHeight + cellHeight / 2 + begin[1]; } else { // otherwise set automatically while (self.used(self.row, self.col)) { self.moveToNextCell(); } x = self.col * cellWidth + cellWidth / 2 + begin[0]; y = self.row * cellHeight + cellHeight / 2 + begin[1]; self.use(self.row, self.col); self.moveToNextCell(); } node.x = x; node.y = y; } });