网络图布局算法
在写课设的时候为了实现前趋图的自动布局,参看了有名的网络图软件gephi,决定使用FR算法对节点进行自动布局。
算法基本思想
FR算法将所有的结点看做是电子,每个结点收到两个力的作用:1. 其他结点的库伦力(斥力)2. 边对点的胡克力(引力)。那么在力的相互作用之下,整个布局最终会称为一个平衡的状态。
算法中将排斥力和吸引力设置为
? \(f_a(d) = \frac{d^2}{k}\) \(f_r(d) = \frac{-k^2}{d}\)
至于两个原子之间的距离d所对应的最佳距离公式定义如下
? \[k = c\sqrt{\frac{area}{number of vectices}} \]
其中 \(area = W*L\) 为了保证在多次迭代中点坐标不会置换出界,c采用模拟退火的方式设置一个temperature来对防止置换出界。
在我实际做时间temperature设置了一个初值,我取了 \(temperture = w/10\)
算法伪代码:
area:= W ? L; {W and L are the width and length of the frame}
G := (V, E); {the vertices are assigned random initial positions}
k := parea/|V |;
function fa(x) := begin return x2/k end;
function fr(x) := begin return k2/x end;
for i := 1 to iterations do begin
{calculate repulsive forces}
for v in V do begin
{each vertex has two vectors: .pos and .disp
v.disp := 0;
for u in V do
if (u 6= v) then begin
{δ is the difference vector between the positions of the two vertices}
δ := v.pos ? u.pos;
v.disp := v.disp + (δ/|δ|) ? fr(|δ|)
end
end
{calculate attractive forces}
for e in E do begin
{each edges is an ordered pair of vertices .vand.u}
δ := e.v.pos ? e.u.pos;
e.v.disp := e.v.disp ? (δ/|δ|) ? fa(|δ|);
e.u.disp := e.u.disp + (δ/|δ|) ? fa(|δ|)
end
{limit max displacement to temperature t and prevent from displacement outside frame}
for v in V do begin
v.pos := v.pos + (v.disp/|v.disp|) ? min(v.disp, t);
v.pos.x := min(W/2, max(?W/2, v.pos.x));
v.pos.y := min(L/2, max(?L/2, v.pos.y))
end
{reduce the temperature as the layout approaches a better configuration}
t := cool(t)
end
我的java 实现
package main.model;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*@Author dyleaf
*@Description: the auto-layout algorithm use Fruchterman and Reingold model
*@Date: 20:23 2018/2/25
*/
public class FruchtermanReingoldLayout {
private int W ; // 画布的宽度
private int L ; //画布的长度
private int temperature = W / 10; //模拟退火初始温度
private int maxIter = 1000; //算法迭代次数
private int area = W * L; //布局大小
private double C = 1; // 节点距离控制系数
private double k; //节点之间的距离
/**
* init FruchtermanReingoldLayout
* @param W the wide of graph
* @param L the length of graph
* @param maxIter the max iterator of the arig
* @param rate define the initial value of temperature
*/
public void init(int W, int L, int maxIter, int rate, double C){
this.W = W;
this.L = L;
this.maxIter = maxIter;
temperature = W/rate;
this.C = C;
}
public List<Node> Run(List<Node> nodes, List<Edge> edges) {
List<Node> reSetNodes = nodes;
for (int i = 0; i < maxIter; i++) {
reSetNodes = springLayout(reSetNodes, edges, i);
}
return reSetNodes;
}
public List<Node> springLayout(List<Node> nodes, List<Edge> edges, int curIter) {
//2计算每次迭代局部区域内两两节点间的斥力所产生的单位位移(一般为正值)
double deltaX, deltaY, deltaLength;
k= C* Math.sqrt(area / (double) nodes.size());
Map<String, Double> dispX = new HashMap<String, Double>();
Map<String, Double> dispY = new HashMap<String, Double>();
for (int v = 0; v < nodes.size(); v++) {
dispX.put(nodes.get(v).getId(), 0.0);
dispY.put(nodes.get(v).getId(), 0.0);
for (int u = 0; u < nodes.size(); u++) {
if (u != v) {
deltaX = nodes.get(v).getX() - nodes.get(u).getX();
if (Double.isNaN(deltaX)) {
System.out.println("x error" + nodes.get(v).getX());
}
deltaY = nodes.get(v).getY() - nodes.get(u).getY();
if (Double.isNaN(deltaY)) {
System.out.println("y error" + nodes.get(v).getX());
}
deltaLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
double force = k * k / deltaLength;
if (Double.isNaN(force)) {
System.err.println("force is NaN node is" + u + "->" + v + "diflength" + deltaLength + "x" + deltaX + "y" + deltaY);
}
String id = nodes.get(v).getId();
dispX.put(id, dispX.get(id) + (deltaX / deltaLength) * force);
dispY.put(id, dispY.get(id) + (deltaY / deltaLength) * force);
}
}
}
//3. 计算每次迭代每条边的引力对两端节点所产生的单位位移(一般为负值)
Node visnodeS = null, visnodeE = null;
for (int e = 0; e < edges.size(); e++) {
String eStartID = edges.get(e).getSourceId();
String eEndID = edges.get(e).getEndId();
visnodeS = getNodeById(nodes, eStartID);
visnodeE = getNodeById(nodes, eEndID);
deltaX = visnodeS.getX() - visnodeE.getX();
deltaY = visnodeS.getY() - visnodeE.getY();
deltaLength = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
double force = deltaLength * deltaLength / k;
if (Double.isNaN(force)) {
System.err.println("force is NaN edge is" + visnodeS.id + "->" + visnodeE.id);
}
double xDisp = (deltaX / deltaLength) * force;
double yDisp = (deltaY / deltaLength) * force;
dispX.put(eStartID, dispX.get(eStartID) - xDisp);
dispY.put(eStartID, dispY.get(eStartID) - yDisp);
dispX.put(eEndID, dispX.get(eEndID) + xDisp);
dispY.put(eEndID, dispY.get(eEndID) + yDisp);
}
//set x,y
for (int v = 0; v < nodes.size(); v++) {
Node node = nodes.get(v);
Double dx = dispX.get(node.getId());
Double dy = dispY.get(node.getId());
Double dispLength = Math.sqrt(dx * dx + dy * dy);
double xDisp = dx / dispLength * Math.min(dispLength, temperature);
double yDisp = dy / dispLength * Math.min(dispLength, temperature);
// don‘t let nodes leave the display
node.setX(node.getX()+xDisp);
node.setY(node.getY()+yDisp);
node.setX(Math.min(W / 2, Math.max(-1.0 * W / 2, node.getX())));
node.setY(Math.min(L / 2, Math.max(-1.0 * L / 2, node.getY())));
}
//cool temperature
cool(curIter);
// temperature*=0.95;
return nodes;
}
private void cool(int curIter) {
temperature *= (1.0 - curIter / (double) maxIter);
}
private Node getNodeById(List<Node> nodes, String id) {
for (Node node : nodes) {
if (node.getId().equals(id)) {
return node;
}
}
return null;
}
}
reference: