@antv/layout
Version:
graph layout algorithm
344 lines (341 loc) • 10.4 kB
JavaScript
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