@antv/g6
Version:
graph visualization frame work
231 lines (193 loc) • 6.05 kB
JavaScript
/**
* @fileOverview random layout
* @author shiwu.wyy@antfin.com
*/
var d3Force = require('d3-force');
var Layout = require('./layout');
var Util = require('../util');
var isArray = require('@antv/util/lib/type/is-array');
var isNumber = require('@antv/util/lib/type/is-number');
var isFunction = require('@antv/util/lib/type/is-function');
/**
* 经典力导布局 force-directed
*/
Layout.registerLayout('force', {
getDefaultCfg: function getDefaultCfg() {
return {
center: [0, 0],
// 向心力作用点
nodeStrength: null,
// 节点作用力
preventOverlap: false,
// 是否防止节点相互覆盖
nodeSize: undefined,
// 节点大小 / 直径,用于防止重叠时的碰撞检测
nodeSpacing: undefined,
// 节点间距,防止节点重叠时节点之间的最小距离(两节点边缘最短距离)
edgeStrength: null,
// 边的作用力, 默认为根据节点的入度出度自适应
linkDistance: 50,
// 默认边长度
forceSimulation: null,
// 自定义 force 方法
alphaDecay: 0.028,
// 迭代阈值的衰减率 [0, 1],0.028 对应最大迭代数为 300
alphaMin: 0.001,
// 停止迭代的阈值
alpha: 0.3,
// 当前阈值
collideStrength: 1,
// 防止重叠的力强度
tick: function tick() {},
onLayoutEnd: function onLayoutEnd() {},
// 布局完成回调
onTick: function onTick() {} // 每一迭代布局回调
};
},
/**
* 初始化
* @param {object} data 数据
*/
init: function init(data) {
var self = this;
self.nodes = data.nodes;
self.edges = data.edges;
self.ticking = false;
},
/**
* 执行布局
*/
execute: function execute() {
var self = this;
var nodes = self.nodes;
var edges = self.edges; // 如果正在布局,忽略布局请求
if (self.ticking) {
return;
}
var simulation = self.forceSimulation;
var alphaMin = self.alphaMin;
var alphaDecay = self.alphaDecay;
var alpha = self.alpha;
if (!simulation) {
try {
// 定义节点的力
var nodeForce = d3Force.forceManyBody();
if (self.nodeStrength) {
nodeForce.strength(self.nodeStrength);
}
simulation = d3Force.forceSimulation().nodes(nodes).force('center', d3Force.forceCenter(self.center[0], self.center[1])).force('charge', nodeForce).alpha(alpha).alphaDecay(alphaDecay).alphaMin(alphaMin).on('tick', function () {
self.tick();
}).on('end', function () {
self.ticking = false;
self.onLayoutEnd && self.onLayoutEnd();
});
if (self.preventOverlap) {
self.overlapProcess(simulation);
} // 如果有边,定义边的力
if (edges) {
// d3 的 forceLayout 会重新生成边的数据模型,为了避免污染源数据
var d3Edges = edges.map(function (edge) {
return {
id: edge.id,
source: edge.source,
target: edge.target
};
});
var edgeForce = d3Force.forceLink().id(function (d) {
return d.id;
}).links(d3Edges);
if (self.edgeStrength) {
edgeForce.strength(self.edgeStrength);
}
if (self.linkDistance) {
edgeForce.distance(self.linkDistance);
}
simulation.force('link', edgeForce);
}
self.forceSimulation = simulation;
self.ticking = true;
} catch (e) {
self.ticking = false;
console.warn(e);
}
} else {
if (self.preventOverlap) {
self.overlapProcess(simulation);
}
simulation.alpha(alpha).restart();
this.ticking = true;
}
},
/**
* 防止重叠
* @param {object} simulation 力模拟模型
*/
overlapProcess: function overlapProcess(simulation) {
var self = this;
var nodeSize = self.nodeSize;
var nodeSizeFunc;
var nodeSpacing = self.nodeSpacing;
var nodeSpacingFunc;
var collideStrength = self.collideStrength;
if (isNumber(nodeSpacing)) {
nodeSpacingFunc = function nodeSpacingFunc() {
return nodeSpacing;
};
} else if (typeof nodeSpacing === 'function') {
nodeSpacingFunc = nodeSpacing;
} else {
nodeSpacingFunc = function nodeSpacingFunc() {
return 0;
};
}
if (!nodeSize) {
nodeSizeFunc = function nodeSizeFunc(d) {
if (d.size) {
if (isArray(d.size)) {
var res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
return res / 2 + nodeSpacingFunc(d);
}
return d.size / 2 + nodeSpacingFunc(d);
}
return 10 + nodeSpacingFunc(d);
};
} else if (isFunction(nodeSize)) {
nodeSizeFunc = nodeSize;
} else if (!isNaN(nodeSize)) {
var radius = nodeSize / 2;
nodeSizeFunc = function nodeSizeFunc(d) {
return radius + nodeSpacingFunc(d);
};
} else if (isArray(nodeSize)) {
var larger = nodeSize[0] > nodeSize[1] ? nodeSize[0] : nodeSize[1];
var _radius = larger / 2;
nodeSizeFunc = function nodeSizeFunc(d) {
return _radius + nodeSpacingFunc(d);
};
} // forceCollide's parameter is a radius
simulation.force('collisionForce', d3Force.forceCollide(nodeSizeFunc).strength(collideStrength));
},
/**
* 更新布局配置,但不执行布局
* @param {object} cfg 需要更新的配置项
*/
updateCfg: function updateCfg(cfg) {
var self = this;
if (self.ticking) {
self.forceSimulation.stop();
self.ticking = false;
}
self.forceSimulation = null;
Util.mix(self, cfg);
},
destroy: function destroy() {
var self = this;
if (self.ticking) {
self.forceSimulation.stop();
self.ticking = false;
}
self.nodes = null;
self.edges = null;
self.destroyed = true;
}
});