FR算法(Fruchterman-Reingold)

网络图布局算法

在写课设的时候为了实现前趋图的自动布局,参看了有名的网络图软件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

posted @ 2018-03-01 23:55  Dyleaf  阅读(6029)  评论(0编辑  收藏  举报