Javascript 如何制作无节点边缘重叠的力导向布局
我正在尝试制作更好的力定向布局算法(针对有向图) 基本算法有效,即满足以下代码中的isStable条件,算法结束,但边缘和节点可能重叠。因此,我想在边缘的中间添加一个虚拟顶点(如下面的图像),它应该解决这个问题,因为虚拟顶点会使边缘排斥其他边缘和节点。 我添加了addDummies方法,它为每个不是循环的边添加一个节点。 我将添加的节点称为中间节点 然后在每次迭代(迭代方法)时,我将中间节点的位置设置为边缘的中间。 其余的是旧算法 我获得了一个更好的布局,没有边缘重叠,但始终不满足结束条件,而且,正如下图所示,图形不是很好,因为中间节点在中心节点周围形成了一种“圆环”(中间节点在红色圆圈内) 我正在寻找一个算法的详细描述,该算法在边上使用虚拟节点,或者寻找任何建议,使算法终止并具有更好的图形(我希望中间节点不要排斥其他节点到外部区域) 是否还要将新边从中间节点添加到旧节点 一个解决方案可能是改变isStable条件,但这个数字通常能保证图形的布局正确,所以我不想碰它 编辑:我以这种方式使用以下代码Javascript 如何制作无节点边缘重叠的力导向布局,javascript,algorithm,graph,force-layout,graph-layout,Javascript,Algorithm,Graph,Force Layout,Graph Layout,我正在尝试制作更好的力定向布局算法(针对有向图) 基本算法有效,即满足以下代码中的isStable条件,算法结束,但边缘和节点可能重叠。因此,我想在边缘的中间添加一个虚拟顶点(如下面的图像),它应该解决这个问题,因为虚拟顶点会使边缘排斥其他边缘和节点。 我添加了addDummies方法,它为每个不是循环的边添加一个节点。 我将添加的节点称为中间节点 然后在每次迭代(迭代方法)时,我将中间节点的位置设置为边缘的中间。 其余的是旧算法 我获得了一个更好的布局,没有边缘重叠,但始终不满足结束条件,而
var layouter = new Graph.Layout.Spring();
while !(layouter.isStable()) {
layouter.iterate(1);
}
以下是当前代码的摘录
Graph.Layout.Spring = function() {
this.maxRepulsiveForceDistance = 10;
this.maxRepulsiveForceDistanceQ = this.maxRepulsiveForceDistance * this.maxRepulsiveForceDistance;
this.k = 2.5;
this.k2 = this.k * this.k;
this.c = 0.01;
this.maxVertexMovement = 0.2;
this.damping = 0.7;
};
Graph.Layout.Spring.prototype = {
resetForUpdate : function() {
this.addDummies();
this.currentIteration = 0;
this.resetVelocities();
},
reset : function() {
this.pastIterations = 0;
this.currentIteration = 0;
this.layoutPrepare();
this.resetForUpdate();
},
isStable: function() {
var nARR= this.graph.nodeArray;
var i = nARR.length -1;
do {
var vel = nARR[i].velocity;
var vx = vel.x;
var vy = vel.y;
var v = vx*vx+vy*vy;
if (v > 0.0002) {
return false;
}
} while (i--);
return true;
},
addDummies: function() {
for (e in this.graph.edges) {
var edge = this.graph.edges[e];
var s = edge.source;
var t = edge.target;
var id = s.id+"#"+t.id;
console.log("adding ", id);
if (!this.graph.nodes[id]) {
if (s.id != t.id) {
this.graph.addNode(id, "");
var node = this.graph.nodes[id];
node.dummy = true;
node.fx = 0;
node.fy = 0;
node.next1id = s.id;
node.next2id = t.id;
node.velocity = {
x: 0,
y: 0
};
}
}
}
},
layoutPrepare : function() {
for ( var i = 0; i < this.graph.nodeArray.length; ++i) {
var node = this.graph.nodeArray[i];
var x = -1+Math.random()*2;
var y = -1+Math.random()*2;
node.layoutPosX = x;
node.layoutPosY = y;
node.fx = 0;
node.fy = 0;
node.velocity = {
x: 0,
y: 0
};
}
},
resetVelocities: function() {
for ( var i = 0; i < this.graph.nodeArray.length; ++i) {
var node = this.graph.nodeArray[i];
node.velocity = {
x: 0,
y: 0
};
}
},
iterate: function(iterations) {
var SQRT = Math.sqrt;
var RAND = Math.random;
var maxRFQ = this.maxRepulsiveForceDistanceQ;
var l_k2 = this.k2;
var it = iterations-1,
i, j, node1, node2;
var L_GRAPH = this.graph;
var L_EDGES = L_GRAPH.edges;
var nodeArray = L_GRAPH.nodeArray;
var L_NLEN = nodeArray.length;
/*
* addition: update midnodes position
*/
for (e in L_GRAPH.edges) {
var edge = L_GRAPH.edges[e];
var s = edge.source;
var t = edge.target;
if (s != t) {
var id = s.id+"#"+t.id;
var midNode = L_GRAPH.nodes[id];
if (midNode) {
var dx = s.layoutPosX - t.layoutPosX;
var dy = s.layoutPosY - t.layoutPosY;
midNode.layoutPosX = s.layoutPosX - dx/2;
midNode.layoutPosY = s.layoutPosY - dy/2;
}
}
}
/*
* repulsive
*/
do {
for (i = 0; i < L_NLEN; ++i) {
node1 = nodeArray[i];
for (j = i+1; j < L_NLEN; ++j) {
node2 = nodeArray[j];
// per cappio
if (node1 === node2)
continue;
var dx = node2.layoutPosX - node1.layoutPosX;
var dy = node2.layoutPosY - node1.layoutPosY;
var d2 = dx * dx + dy * dy;
if (d2 < 0.001) {
dx = 0.1 * RAND() + 0.1;
dy = 0.1 * RAND() + 0.1;
d2 = dx * dx + dy * dy;
}
if (d2 < maxRFQ) {
var d = SQRT(d2);
var f = 2*(l_k2 / d2);
var xx = f * dx / d;
var yy = f * dy / d;
node2.fx += xx;
node2.fy += yy;
node1.fx -= xx;
node1.fy -= yy;
}
} // for j
} // for i
/*
* Attractive
*/
i = (L_EDGES.length)-1;
if (i >= 0) {
do {
var edge = L_EDGES[i];
var node1 = edge.source;
var node2 = edge.target;
// evita self-force, che cmq andrebbe a zero
if (node1 === node2)
continue;
var dx = node2.layoutPosX - node1.layoutPosX;
var dy = node2.layoutPosY - node1.layoutPosY;
var d2 = dx * dx + dy * dy;
if (d2 < 0.01) {
dx = 0.1 * RAND() + 0.1;
dy = 0.1 * RAND() + 0.1;
d2 = dx * dx + dy * dy;
}
d = SQRT(d2);
var f = (l_k2-d2)/l_k2;
var n2d = node2.edges.length;
if (n2d > 2) {
n2d = 2;
}
var n1d = node1.edges.length;
if (n1d > 2) {
n1d = 2;
}
var xcomp = f * dx/d;
var ycomp = f * dy/d;
node2.fx += xcomp / n2d;
node2.fy += ycomp / n2d;
node1.fx -= xcomp / n1d;
node1.fy -= ycomp / n1d;
} while (i--);
} // if i>=0
/*
* Move by the given force
*/
var max = this.maxVertexMovement;
var d = this.damping;
var c = this.c;
var i = L_NLEN-1;
do {
var node = nodeArray[i];
var xmove,
ymove;
var v = node.velocity;
v.x = v.x * d + node.fx * c;
v.y = v.y * d + node.fy * c;
xmove = v.x;
ymove = v.y;
if (xmove > max)
xmove = max;
else if (xmove < -max)
xmove = -max;
if (ymove > max)
ymove = max;
else if (ymove < -max)
ymove = -max;
if (node.isNailed !== undefined) {
v.x = 0;
v.y = 0;
} else {
v.x = xmove;
v.y = ymove;
node.layoutPosX += xmove;
node.layoutPosY += ymove;
}
node.fx = 0;
node.fy = 0;
} while (i--);
} while (it--);
},
Graph.Layout.Spring=函数(){
这是最大斥力距离=10;
this.maxExcepriveForceDistanceQ=this.maxExcepriveForceDistance*this.maxExcepriveForceDistance;
该系数k=2.5;
this.k2=this.k*this.k;
该系数c=0.01;
此参数为.maxVertexMovement=0.2;
该阻尼=0.7;
};
Graph.Layout.Spring.prototype={
resetForUpdate:function(){
这个.addDummies();
此.currentIteration=0;
这个;
},
重置:函数(){
此参数为0;
此.currentIteration=0;
这个.layoutpready();
这是resetForUpdate();
},
isStable:函数(){
var nARR=this.graph.noderray;
var i=平均长度-1;
做{
var-vel=nARR[i].速度;
var vx=等级x;
var vy=y级;
var v=vx*vx+vy*vy;
如果(v>0.0002){
返回false;
}
}而(我--),;
返回true;
},
addDummies:function(){
对于(本图中的e.边){
var-edge=this.graph.edges[e];
var s=edge.source;
var t=edge.target;
var id=s.id+“#”+t.id;
console.log(“添加”,id);
如果(!this.graph.nodes[id]){
如果(s.id!=t.id){
this.graph.addNode(id为“”);
var node=this.graph.nodes[id];
node.dummy=true;
node.fx=0;
node.fy=0;
node.next1id=s.id;
node.next2id=t.id;
节点速度={
x:0,,
y:0
};
}
}
}
},
布局准备:函数(){
对于(var i=0;i=0){
做{
physics: {
enabled: { boolean: bool },
barnesHut: {
gravitationalConstant: { number },
centralGravity: { number },
springLength: { number },
springConstant: { number },
damping: { number },
avoidOverlap: { number },
__type__: { object }
},
forceAtlas2Based: {
gravitationalConstant: { number },
centralGravity: { number },
springLength: { number },
springConstant: { number },
damping: { number },
avoidOverlap: { number },
__type__: { object }
},
repulsion: {
centralGravity: { number },
springLength: { number },
springConstant: { number },
nodeDistance: { number },
damping: { number },
__type__: { object }
},
hierarchicalRepulsion: {
centralGravity: { number },
springLength: { number },
springConstant: { number },
nodeDistance: { number },
damping: { number },
avoidOverlap: { number },
__type__: { object }
},
maxVelocity: { number },
minVelocity: { number }, // px/s
solver: { string: ['barnesHut', 'repulsion', 'hierarchicalRepulsion', 'forceAtlas2Based'] },
stabilization: {
enabled: { boolean: bool },
iterations: { number }, // maximum number of iteration to stabilize
updateInterval: { number },
onlyDynamicEdges: { boolean: bool },
fit: { boolean: bool },
__type__: { object, boolean: bool }
},
timestep: { number },
adaptiveTimestep: { boolean: bool },
__type__: { object, boolean: bool }