FR算法(Fruchterman-Reingold)
阿新 • • 發佈:2018-03-02
每次 -m width random esc isnan util 兩個 vertex
網絡圖布局算法
在寫課設的時候為了實現前趨圖的自動布局,參看了有名的網絡圖軟件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:
Force-Directed Drawing Algorithms
FR算法(Fruchterman-Reingold)