1. 程式人生 > >FR算法(Fruchterman-Reingold)

FR算法(Fruchterman-Reingold)

每次 -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)