import { __awaiter } from "tslib"; import { cloneFormatData, formatNumberFn, formatSizeFn } from './util'; import { handleSingleNodeGraph } from './util/common'; const DEFAULTS_LAYOUT_OPTIONS = { radius: null, startRadius: null, endRadius: null, startAngle: 0, endAngle: 2 * Math.PI, clockwise: true, divisions: 1, ordering: null, angleRatio: 1, }; /** * 环形布局 * * Circular layout */ export class CircularLayout { constructor(options = {}) { this.options = options; this.id = 'circular'; this.options = Object.assign(Object.assign({}, DEFAULTS_LAYOUT_OPTIONS), options); } /** * Return the positions of nodes and edges(if needed). */ execute(graph, options) { return __awaiter(this, void 0, void 0, function* () { return this.genericCircularLayout(false, graph, options); }); } /** * To directly assign the positions to the nodes. */ assign(graph, options) { return __awaiter(this, void 0, void 0, function* () { yield this.genericCircularLayout(true, graph, options); }); } genericCircularLayout(assign, graph, options) { return __awaiter(this, void 0, void 0, function* () { const mergedOptions = Object.assign(Object.assign({}, this.options), options); const { width, height, center, divisions, startAngle = 0, endAngle = 2 * Math.PI, angleRatio, ordering, clockwise, nodeSpacing: paramNodeSpacing, nodeSize: paramNodeSize, } = mergedOptions; const nodes = graph.getAllNodes(); const edges = graph.getAllEdges(); // Calculate center according to `window` if not provided. const [calculatedWidth, calculatedHeight, calculatedCenter] = calculateCenter(width, height, center); const n = nodes === null || nodes === void 0 ? void 0 : nodes.length; if (!n || n === 1) { return handleSingleNodeGraph(graph, assign, calculatedCenter); } const angleStep = (endAngle - startAngle) / n; let { radius, startRadius, endRadius } = mergedOptions; if (paramNodeSpacing) { const nodeSpacing = formatNumberFn(10, paramNodeSpacing); const nodeSize = formatSizeFn(10, paramNodeSize); let maxNodeSize = -Infinity; nodes.forEach((node) => { const nSize = nodeSize(node); if (maxNodeSize < nSize) maxNodeSize = nSize; }); let perimeter = 0; nodes.forEach((node, i) => { if (i === 0) perimeter += maxNodeSize || 10; else perimeter += (nodeSpacing(node) || 0) + (maxNodeSize || 10); }); radius = perimeter / (2 * Math.PI); } else if (!radius && !startRadius && !endRadius) { radius = Math.min(calculatedHeight, calculatedWidth) / 2; } else if (!startRadius && endRadius) { startRadius = endRadius; } else if (startRadius && !endRadius) { endRadius = startRadius; } const astep = angleStep * angleRatio; // calculated nodes as temporary result let layoutNodes = []; if (ordering === 'topology') { // layout according to the topology layoutNodes = topologyOrdering(graph, nodes); } else if (ordering === 'topology-directed') { // layout according to the topology layoutNodes = topologyOrdering(graph, nodes, true); } else if (ordering === 'degree') { // layout according to the descent order of degrees layoutNodes = degreeOrdering(graph, nodes); } else { // layout according to the original order in the data.nodes layoutNodes = nodes.map((node) => cloneFormatData(node)); } const divN = Math.ceil(n / divisions); // node number in each division for (let i = 0; i < n; ++i) { let r = radius; if (!r && startRadius !== null && endRadius !== null) { r = startRadius + (i * (endRadius - startRadius)) / (n - 1); } if (!r) { r = 10 + (i * 100) / (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].data.x = calculatedCenter[0] + Math.cos(angle) * r; layoutNodes[i].data.y = calculatedCenter[1] + Math.sin(angle) * r; } if (assign) { layoutNodes.forEach((node) => { graph.mergeNodeData(node.id, { x: node.data.x, y: node.data.y, }); }); } const result = { nodes: layoutNodes, edges, }; return result; }); } } /** * order the nodes acoording to the graph topology * @param graph * @param nodes * @param directed * @returns */ const topologyOrdering = (graph, nodes, directed = false) => { const orderedCNodes = [cloneFormatData(nodes[0])]; const pickFlags = {}; const n = nodes.length; pickFlags[nodes[0].id] = true; // write children into cnodes let k = 0; nodes.forEach((node, i) => { if (i !== 0) { if ((i === n - 1 || graph.getDegree(node.id, 'both') !== graph.getDegree(nodes[i + 1].id, 'both') || graph.areNeighbors(orderedCNodes[k].id, node.id)) && !pickFlags[node.id]) { orderedCNodes.push(cloneFormatData(node)); pickFlags[node.id] = true; k++; } else { const children = directed ? graph.getSuccessors(orderedCNodes[k].id) : graph.getNeighbors(orderedCNodes[k].id); let foundChild = false; for (let j = 0; j < children.length; j++) { const child = children[j]; if (graph.getDegree(child.id) === graph.getDegree(node.id) && !pickFlags[child.id]) { orderedCNodes.push(cloneFormatData(child)); pickFlags[child.id] = true; foundChild = true; break; } } let ii = 0; while (!foundChild) { if (!pickFlags[nodes[ii].id]) { orderedCNodes.push(cloneFormatData(nodes[ii])); pickFlags[nodes[ii].id] = true; foundChild = true; } ii++; if (ii === n) { break; } } } } }); return orderedCNodes; }; /** * order the nodes according to their degree * @param graph * @param nodes * @returns */ function degreeOrdering(graph, nodes) { const orderedNodes = []; nodes.forEach((node, i) => { orderedNodes.push(cloneFormatData(node)); }); orderedNodes.sort((nodeA, nodeB) => graph.getDegree(nodeA.id, 'both') - graph.getDegree(nodeB.id, 'both')); return orderedNodes; } /** * format the invalide width and height, and get the center position * @param width * @param height * @param center * @returns */ const calculateCenter = (width, height, center) => { let calculatedWidth = width; let calculatedHeight = height; let calculatedCenter = center; if (!calculatedWidth && typeof window !== 'undefined') { calculatedWidth = window.innerWidth; } if (!calculatedHeight && typeof window !== 'undefined') { calculatedHeight = window.innerHeight; } if (!calculatedCenter) { calculatedCenter = [calculatedWidth / 2, calculatedHeight / 2]; } return [calculatedWidth, calculatedHeight, calculatedCenter]; }; //# sourceMappingURL=circular.js.map