• FR算法(Fruchterman-Reingold)


    网络图布局算法

    在写课设的时候为了实现前趋图的自动布局,参看了有名的网络图软件gephi,决定使用FR算法对节点进行自动布局。

    算法基本思想

    FR算法将所有的结点看做是电子,每个结点收到两个力的作用:1. 其他结点的库伦力(斥力)2. 边对点的胡克力(引力)。那么在力的相互作用之下,整个布局最终会称为一个平衡的状态。

    算法中将排斥力和吸引力设置为

    (f_a(d) = frac{d^2}{k}) (f_r(d) = frac{-k^2}{d})

    至于两个原子之间的距离d所对应的最佳距离公式定义如下

    ​ $$k = csqrt{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

  • 相关阅读:
    如何评价ionic和react native?
    ionic 之 基本布局
    TensorFlow中文社区---下载与安装
    深入代码详谈irqbalance【转】
    用Gen4消除电容触摸屏设计屏障【转】
    如何解决触摸屏的电磁干扰问题【转】
    Linux firmware 加载【转】
    Linux Shell 文本处理工具集锦【转】
    刷leetcode是什么样的体验?【转】
    知乎上的一些文章---leetcode【笔记1】
  • 原文地址:https://www.cnblogs.com/Dyleaf/p/8491136.html
Copyright © 2020-2023  润新知