UNPKG

@antv/layout

Version:
344 lines (341 loc) 10.4 kB
import forceSimulation from '../../node_modules/d3-force/src/simulation.js'; import forceX from '../../node_modules/d3-force/src/x.js'; import forceY from '../../node_modules/d3-force/src/y.js'; import forceCollide from '../../node_modules/d3-force/src/collide.js'; import forceManyBody from '../../node_modules/d3-force/src/manyBody.js'; import forceLink from '../../node_modules/d3-force/src/link.js'; const getEdgeTerminal = (edge, type) => { const terminal = edge[type]; return terminal; }; // https://github.com/john-guerra/forceInABox/blob/master/src/forceInABox.js function forceInABox() { function constant(_) { return () => _; } let groupBy = (d) => { return d.cluster; // return d.group; }; let forceNodeSize = constant(1); let forceCharge = constant(-1); let forceLinkDistance = constant(100); let forceLinkStrength = constant(0.1); let offset = [0, 0]; let nodes = []; let nodesMap = {}; let links = []; let centerX = 100; let centerY = 100; let foci = { none: { x: 0, y: 0, }, }; let templateNodes = []; let templateForce; let template = 'force'; let enableGrouping = true; let strength = 0.1; function force(alpha) { if (!enableGrouping) { return force; } templateForce.tick(); getFocisFromTemplate(); for (let i = 0, n = nodes.length, node, k = alpha * strength; i < n; ++i) { node = nodes[i]; node.vx || (node.vx = 0); node.vy || (node.vy = 0); node.vx += (foci[groupBy(node._original)].x - node.x) * k; node.vy += (foci[groupBy(node._original)].y - node.y) * k; } } function initialize() { if (!nodes) return; initializeWithForce(); } function initializeWithForce() { if (!nodes || !nodes.length) { return; } const node = nodes[0]; if (groupBy(node._original) === undefined) { throw Error("Couldnt find the grouping attribute for the nodes. Make sure to set it up with forceInABox.groupBy('clusterAttr') before calling .links()"); } // checkLinksAsObjects(); const net = getGroupsGraph(); templateForce = forceSimulation(net.nodes) .force('x', forceX(centerX).strength(0.1)) .force('y', forceY(centerY).strength(0.1)) .force('collide', forceCollide((d) => d.r).iterations(4)) .force('charge', forceManyBody().strength(forceCharge)) .force('links', forceLink(net.nodes.length ? net.links : []) .distance(forceLinkDistance) .strength(forceLinkStrength)); templateNodes = templateForce.nodes(); getFocisFromTemplate(); } function getGroupsGraph() { const gnodes = []; const glinks = []; const dNodes = {}; let clustersList = []; let clustersCounts = {}; let clustersLinks = []; clustersCounts = computeClustersNodeCounts(nodes); clustersLinks = computeClustersLinkCounts(links); clustersList = Object.keys(clustersCounts); clustersList.forEach((key, index) => { const val = clustersCounts[key]; // Uses approx meta-node size gnodes.push({ id: key, size: val.count, r: Math.sqrt(val.sumforceNodeSize / Math.PI), }); dNodes[key] = index; }); clustersLinks.forEach((l) => { const sourceTerminal = getEdgeTerminal(l, 'source'); const targetTerminal = getEdgeTerminal(l, 'target'); const source = dNodes[sourceTerminal]; const target = dNodes[targetTerminal]; if (source !== undefined && target !== undefined) { glinks.push({ source, target, count: l.count, }); } }); return { nodes: gnodes, links: glinks, }; } function computeClustersNodeCounts(nodes) { const clustersCounts = {}; nodes.forEach((d) => { const key = groupBy(d._original); if (!clustersCounts[key]) { clustersCounts[key] = { count: 0, sumforceNodeSize: 0, }; } }); nodes.forEach((d) => { const key = groupBy(d._original); const nodeSize = forceNodeSize(d._original); const tmpCount = clustersCounts[key]; tmpCount.count = tmpCount.count + 1; tmpCount.sumforceNodeSize = tmpCount.sumforceNodeSize + Math.PI * (nodeSize * nodeSize) * 1.3; clustersCounts[key] = tmpCount; }); return clustersCounts; } function computeClustersLinkCounts(links) { const dClusterLinks = {}; const clusterLinks = []; links.forEach((l) => { const key = getLinkKey(l); let count = 0; if (dClusterLinks[key] !== undefined) { count = dClusterLinks[key]; } count += 1; dClusterLinks[key] = count; }); // @ts-ignore const entries = Object.entries(dClusterLinks); entries.forEach(([key, count]) => { const source = key.split('~')[0]; const target = key.split('~')[1]; if (source !== undefined && target !== undefined) { clusterLinks.push({ source, target, count, }); } }); return clusterLinks; } function getFocisFromTemplate() { foci = { none: { x: 0, y: 0, }, }; templateNodes.forEach((d) => { foci[d.id] = { x: d.x - offset[0], y: d.y - offset[1], }; }); return foci; } function getLinkKey(l) { const source = getEdgeTerminal(l, 'source'); const target = getEdgeTerminal(l, 'target'); const sourceID = groupBy(nodesMap[source]._original); const targetID = groupBy(nodesMap[target]._original); return sourceID <= targetID ? `${sourceID}~${targetID}` : `${targetID}~${sourceID}`; } function genNodesMap(nodes) { nodesMap = {}; nodes.forEach((node) => { nodesMap[node.id] = node; }); } function setTemplate(x) { if (!arguments.length) return template; template = x; initialize(); return force; } function setGroupBy(x) { if (!arguments.length) return groupBy; if (typeof x === 'string') { groupBy = (d) => { return d[x]; }; return force; } groupBy = x; return force; } function setEnableGrouping(x) { if (!arguments.length) return enableGrouping; enableGrouping = x; return force; } function setStrength(x) { if (!arguments.length) return strength; strength = x; return force; } function setCenterX(_) { if (arguments.length) { centerX = _; return force; } return centerX; } function setCenterY(_) { if (arguments.length) { centerY = _; return force; } return centerY; } function setNodes(_) { if (arguments.length) { genNodesMap(_ || []); nodes = _ || []; return force; } return nodes; } function setLinks(_) { if (arguments.length) { links = _ || []; initialize(); return force; } return links; } function setForceNodeSize(_) { if (arguments.length) { if (typeof _ === 'function') { forceNodeSize = _; } else { forceNodeSize = constant(+_); } initialize(); return force; } return forceNodeSize; } function setForceCharge(_) { if (arguments.length) { if (typeof _ === 'function') { forceCharge = _; } else { forceCharge = constant(+_); } initialize(); return force; } return forceCharge; } function setForceLinkDistance(_) { if (arguments.length) { if (typeof _ === 'function') { forceLinkDistance = _; } else { forceLinkDistance = constant(+_); } initialize(); return force; } return forceLinkDistance; } function setForceLinkStrength(_) { if (arguments.length) { if (typeof _ === 'function') { forceLinkStrength = _; } else { forceLinkStrength = constant(+_); } initialize(); return force; } return forceLinkStrength; } function setOffset(_) { if (arguments.length) { offset = _; return force; } return offset; } force.initialize = (_) => { nodes = _; initialize(); }; force.template = setTemplate; force.groupBy = setGroupBy; force.enableGrouping = setEnableGrouping; force.strength = setStrength; force.centerX = setCenterX; force.centerY = setCenterY; force.nodes = setNodes; force.links = setLinks; force.forceNodeSize = setForceNodeSize; // Legacy support force.nodeSize = force.forceNodeSize; force.forceCharge = setForceCharge; force.forceLinkDistance = setForceLinkDistance; force.forceLinkStrength = setForceLinkStrength; force.offset = setOffset; force.getFocis = getFocisFromTemplate; return force; } export { forceInABox as default, getEdgeTerminal }; //# sourceMappingURL=force-in-a-box.js.map