├── pics ├── graph1.png ├── graph2.png └── graph3.png ├── java ├── data │ ├── DataSet2.java │ ├── DataSet3.java │ ├── DataSet4.java │ └── DataSet1.java ├── Edge.java ├── Node.java ├── Utils.java ├── Main.java └── CollisionGenerator.java ├── LICENSE ├── README.md └── javascript └── force-directed.js /pics/graph1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaangliu/ForceDirectedLayout/HEAD/pics/graph1.png -------------------------------------------------------------------------------- /pics/graph2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaangliu/ForceDirectedLayout/HEAD/pics/graph2.png -------------------------------------------------------------------------------- /pics/graph3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaangliu/ForceDirectedLayout/HEAD/pics/graph3.png -------------------------------------------------------------------------------- /java/data/DataSet2.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java.data; 2 | 3 | 4 | public class DataSet2 { 5 | public static String dataSet = 6 | "0 1;" + 7 | "0 2;" + 8 | "0 3;" + 9 | "0 4;" + 10 | "0 5;" + 11 | "0 6;" + 12 | "0 7;" + 13 | "0 8;" + 14 | "0 9;"; 15 | } 16 | -------------------------------------------------------------------------------- /java/Edge.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java; 2 | 3 | class Edge { 4 | private String source; 5 | private String target; 6 | 7 | Edge(int source, int target) { 8 | this.source = source + ""; 9 | this.target = target + ""; 10 | } 11 | 12 | String getSource() { 13 | return source; 14 | } 15 | 16 | String getTarget() { 17 | return target; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /java/data/DataSet3.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java.data; 2 | 3 | 4 | public class DataSet3 { 5 | public static String dataSet = 6 | "0 1;" + 7 | "0 2;" + 8 | "0 3;" + 9 | "0 4;" + 10 | "0 5;" + 11 | "0 6;" + 12 | "0 7;" + 13 | "0 8;" + 14 | "0 9;" + 15 | "0 10;" + 16 | "0 11;" + 17 | "0 12;" + "0 13;" + "0 14;" + "0 15;" + "0 16;" + "0 17;" + "0 18;" + "0 19;"; 18 | } 19 | -------------------------------------------------------------------------------- /java/data/DataSet4.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java.data; 2 | 3 | 4 | public class DataSet4 { 5 | public static String dataSet = 6 | "0 1;" + 7 | "0 2;" + 8 | "0 3;" + 9 | "0 4;" + 10 | "0 5;" + 11 | "0 6;" + 12 | "0 7;" + 13 | "0 8;" + 14 | "0 9;" + 15 | "0 10;" + 16 | "0 11;" + 17 | "0 12;" + "0 13;" + "0 14;" + "0 15;" + "0 16;" + "0 17;" + "0 18;" + "0 19;" + "1 20;" + "2 21;" + "3 22;" + "3 23"; 18 | } 19 | -------------------------------------------------------------------------------- /java/Node.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java; 2 | 3 | class Node { 4 | private double x; 5 | private double y; 6 | private String id; 7 | 8 | Node(int id) { 9 | this.id = id + ""; 10 | } 11 | 12 | void setXPosition(double x) { 13 | this.x = Utils.getInt(x); 14 | } 15 | 16 | void setYPosition(double y) { 17 | this.y = Utils.getInt(y); 18 | } 19 | 20 | double getX() { 21 | return x; 22 | } 23 | 24 | double getY() { 25 | return y; 26 | } 27 | 28 | String getKey() { 29 | return id; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /java/Utils.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java; 2 | 3 | import java.math.BigDecimal; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import github.ForceDirectLayout.java.data.DataSet1; 8 | 9 | class Utils { 10 | //四舍五入把double转化int整型,0.5进一,小于0.5不进一 11 | static int getInt(double number) { 12 | BigDecimal bd = new BigDecimal(number).setScale(0, BigDecimal.ROUND_HALF_UP); 13 | return Integer.parseInt(bd.toString()); 14 | } 15 | 16 | static List getEdges() { 17 | String dataSet = getDataSet(); 18 | if (dataSet == null) { 19 | return null; 20 | } 21 | String notRaw[] = dataSet.split(";"); 22 | List edgeList = new ArrayList<>(); 23 | for (String str : notRaw) { 24 | String[] pair = str.split(" "); 25 | edgeList.add(new Edge(Integer.parseInt(pair[0]), Integer.parseInt(pair[1]))); 26 | } 27 | return edgeList; 28 | } 29 | 30 | private static String getDataSet() { 31 | return DataSet1.dataSet; 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 知还 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /java/Main.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Main { 9 | private static List sNodeArrayList = new ArrayList<>(); 10 | private static List sEdgeArrayList; 11 | 12 | public static void main(String[] args) { 13 | getData(); 14 | // getRandomData(); 15 | //随机生成坐标. Generate coordinates randomly. 16 | double initialX, initialY, initialSize = 40.0; 17 | for (Node node : sNodeArrayList) { 18 | initialX = 0 + CollisionGenerator.CANVAS_WIDTH * .5; 19 | initialY = 0 + CollisionGenerator.CANVAS_HEIGHT * .5; 20 | node.setXPosition(initialX + initialSize * (Math.random() - .5)); 21 | node.setYPosition(initialY + initialSize * (Math.random() - .5)); 22 | } 23 | 24 | CollisionGenerator collisionGenerator = new CollisionGenerator(sNodeArrayList, sEdgeArrayList); 25 | //迭代200次. Iterate 200 times. 26 | for (int i = 0; i < 200; i++) { 27 | collisionGenerator.collide(); 28 | } 29 | ResultEntity resultEntity = new ResultEntity(); 30 | resultEntity.nodes = collisionGenerator.getNodeList(); 31 | resultEntity.links = sEdgeArrayList; 32 | String res = new Gson().toJson(resultEntity); 33 | System.out.println(res); 34 | } 35 | 36 | static class ResultEntity { 37 | List nodes; 38 | List links; 39 | } 40 | 41 | /** 42 | * Manually constructed data. For reference only. 43 | */ 44 | private static void getData() { 45 | sEdgeArrayList = Utils.getEdges(); 46 | for (int i = 0; i < 100; i++) { 47 | sNodeArrayList.add(new Node(i)); 48 | } 49 | } 50 | 51 | /** 52 | * 随机构造一些node和的edge。For reference only. 53 | */ 54 | private static void getRandomData() { 55 | sEdgeArrayList = new ArrayList<>(); 56 | for (int i = 0; i < 20; i++) { 57 | Node node = new Node(i); 58 | sNodeArrayList.add(node); 59 | } 60 | for (int i = 0; i < 20; i++) { 61 | int edgeCount = (int) (Math.random() * 8); 62 | System.out.println(edgeCount); 63 | for (int j = 0; j < edgeCount; j++) { 64 | int targetId = (int) (Math.random() * 20); 65 | Edge edge = new Edge(i, targetId); 66 | sEdgeArrayList.add(edge); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 描述 2 | >Force-Directed Layout algorithms are graph drawing algorithms based only on information contained within the structure of the graph itself rather than relying on contextual information. 3 | 4 | 力导向布局算法是一类绘图算法,它仅仅基于图的解构本身来绘图,而不依赖于上下文信息。 5 | 6 | [力导向绘图 (Force-directed graph drawing)](https://en.wikipedia.org/wiki/Force-directed_graph_drawing)可以用于描述关系图的结点之间的关系,把结点分布到画布上合理的位置,比如描述企业之间的关系,社交网络中的人际关系等。 7 | 8 | ## 原理 9 | ### 斥力(Repulsive Force) 10 | 把每个节点看做一个电荷,电荷与电荷之间存在斥力,也就是库仑力,根据库仑定律( [Coulomb's law](https://en.wikipedia.org/wiki/Coulomb%27s_law "Coulomb's law")),电子之间的斥力可以这么计算: 11 | ![Coulomb's law](https://upload-images.jianshu.io/upload_images/3326381-7357589396de7639.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 12 | 13 | 假设每个电子的电量都是1,那就有: 14 | >F = k/r2. 15 | 16 | 常数k可以根据画布大小和电子数量计算。 17 | 由于需要更新x,y坐标,可以分别计算斥力产生的正向位移。 18 | ``` 19 | displacementX = distX / dist * k * k / dist * ejectFactor 20 | ``` 21 | *关于计算x, y偏移和常数k的方式,可能并没有特别明确的方式,这里可能并不是最优的方法。 22 | 23 | ### 引力(Traction Force) 24 | 一些粒子之间被一些边所牵连,这些边产生类似弹簧的胡克引力: 25 | >Fs=ks(x−x0) 26 | 27 | 牵制着边两端的粒子。斥力和引力不断作用,粒子在不断位移之后趋于平衡,逐渐不再发生相对位移,能量不断消耗,最终趋于零。 28 | 29 | 30 | 在引力和斥力地作用下不断地更新坐标,经过多次迭代达到一个稳定状态,收敛结束。参数和迭代次数需要 31 | 调试。 32 | 33 | 34 | 分别用Java和JavaScript简单实现了这个算法,利用[D3](https://bl.ocks.org/mbostock/4557698)做图形化展示,下面是一些跑出来的数据呈现出的分布图: 35 | 36 | ![Graph 1. Iteration simulation of 24 nodes.](https://upload-images.jianshu.io/upload_images/3326381-bd63ba0e18ed8cd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 37 | Graph 1. Iteration simulation of 24 nodes. 38 | 39 | ![Graph 2. Randomly generated 20 nodes, each having 1~7 links.](https://upload-images.jianshu.io/upload_images/3326381-079db14045a33e5f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 40 | Graph 2. Randomly generated 20 nodes, each having 1~7 links. 41 | 42 | ![Graph 3. Simulation of 100 nodes, iterating for 200 times.](https://upload-images.jianshu.io/upload_images/3326381-c0fa4820e7dd5ea5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 43 | Graph 3. Simulation of 100 nodes, iterating for 200 times. 44 | 45 | --- 46 | Refs: 47 | 48 | [1]https://www.ibm.com/developerworks/cn/web/0909_fudl_flashsocial/#major3 49 | 50 | [2]http://zhenghaoju700.blog.163.com/blog/static/13585951820114153548541/ 51 | 52 | [3]https://blog.csdn.net/newworld123made/article/details/51443603 53 | 54 | [4]http://philogb.github.io/blog/2009/09/30/force-directed-layouts/ 55 | 56 | [5]https://bl.ocks.org/mbostock/4557698 57 | 58 | -------------------------------------------------------------------------------- /java/CollisionGenerator.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Collision generator by liuchang. 10 | */ 11 | class CollisionGenerator { 12 | static final int CANVAS_WIDTH = 1000; 13 | static final int CANVAS_HEIGHT = 1000; 14 | private List mNodeList; 15 | private List mEdgeList; 16 | private Map mDxMap = new HashMap<>(); 17 | private Map mDyMap = new HashMap<>(); 18 | private Map mNodeMap = new HashMap<>(); 19 | private double k; 20 | 21 | 22 | CollisionGenerator(List nodeList, List edgeList) { 23 | this.mNodeList = nodeList; 24 | this.mEdgeList = edgeList; 25 | if (nodeList != null && nodeList.size() != 0) { 26 | k = Math.sqrt(CANVAS_WIDTH * CANVAS_HEIGHT / (double) mNodeList.size()); 27 | } 28 | if (nodeList != null) { 29 | for (int i = 0; i < nodeList.size(); i++) { 30 | Node node = nodeList.get(i); 31 | if (node != null) { 32 | mNodeMap.put(node.getKey(), node); 33 | } 34 | } 35 | } 36 | } 37 | 38 | void collide() { 39 | calculateRepulsive(); 40 | calculateTraction(); 41 | updateCoordinates(); 42 | } 43 | 44 | /** 45 | * 计算两个Node的斥力产生的单位位移。 46 | * Calculate the displacement generated by the repulsive force between two nodes.* 47 | */ 48 | private void calculateRepulsive() { 49 | int ejectFactor = 6;// default: 6 50 | double distX, distY, dist; 51 | for (int v = 0; v < mNodeList.size(); v++) { 52 | mDxMap.put(mNodeList.get(v).getKey(), 0.0); 53 | mDyMap.put(mNodeList.get(v).getKey(), 0.0); 54 | for (int u = 0; u < mNodeList.size(); u++) { 55 | if (u != v) { 56 | distX = mNodeList.get(v).getX() - mNodeList.get(u).getX(); 57 | distY = mNodeList.get(v).getY() - mNodeList.get(u).getY(); 58 | dist = Math.sqrt(distX * distX + distY * distY); 59 | if (dist < 30) { 60 | ejectFactor = 5; 61 | } 62 | if (dist > 0 && dist < 250) { 63 | String id = mNodeList.get(v).getKey(); 64 | mDxMap.put(id, mDxMap.get(id) + distX / dist * k * k / dist * ejectFactor); 65 | mDyMap.put(id, mDyMap.get(id) + distY / dist * k * k / dist * ejectFactor); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * 计算Edge的引力对两端Node产生的引力。 74 | * Calculate the traction force generated by the edge acted on the two nodes of its two ends. 75 | */ 76 | private void calculateTraction() { 77 | int condenseFactor = 3; 78 | Node startNode, endNode; 79 | for (int e = 0; e < mEdgeList.size(); e++) { 80 | String eStartID = mEdgeList.get(e).getSource(); 81 | String eEndID = mEdgeList.get(e).getTarget(); 82 | startNode = mNodeMap.get(eStartID); 83 | endNode = mNodeMap.get(eEndID); 84 | if (startNode == null) { 85 | System.out.println("Cannot find node id: " + eStartID + ", please check it out."); 86 | return; 87 | } 88 | if (endNode == null) { 89 | System.out.println("Cannot find node id: " + eEndID + ", please check it out."); 90 | return; 91 | } 92 | double distX, distY, dist; 93 | distX = startNode.getX() - endNode.getX(); 94 | distY = startNode.getY() - endNode.getY(); 95 | dist = Math.sqrt(distX * distX + distY * distY); 96 | mDxMap.put(eStartID, mDxMap.get(eStartID) - distX * dist / k * condenseFactor); 97 | mDyMap.put(eStartID, mDyMap.get(eStartID) - distY * dist / k * condenseFactor); 98 | mDxMap.put(eEndID, mDxMap.get(eEndID) + distX * dist / k * condenseFactor); 99 | mDyMap.put(eEndID, mDyMap.get(eEndID) + distY * dist / k * condenseFactor); 100 | } 101 | } 102 | 103 | /** 104 | * 更新坐标。 105 | * update the coordinates. 106 | */ 107 | private void updateCoordinates() { 108 | int maxt = 4, maxty = 3; //Additional coefficients. 109 | for (int v = 0; v < mNodeList.size(); v++) { 110 | Node node = mNodeList.get(v); 111 | int dx = (int) Math.floor(mDxMap.get(node.getKey())); 112 | int dy = (int) Math.floor(mDyMap.get(node.getKey())); 113 | 114 | if (dx < -maxt) dx = -maxt; 115 | if (dx > maxt) dx = maxt; 116 | if (dy < -maxty) dy = -maxty; 117 | if (dy > maxty) dy = maxty; 118 | 119 | node.setXPosition((node.getX() + dx) >= CANVAS_WIDTH || (node.getX() + dx) <= 0 ? node.getX() - dx : node.getX() + dx); 120 | node.setYPosition((node.getY() + dy) >= CANVAS_HEIGHT || (node.getY() + dy <= 0) ? node.getY() - dy : node.getY() + dy); 121 | } 122 | } 123 | 124 | List getNodeList() { 125 | return mNodeList == null ? new ArrayList() : mNodeList; 126 | } 127 | } -------------------------------------------------------------------------------- /javascript/force-directed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A force directed graph layout implementation by liuchang on 2018/05/10. 3 | */ 4 | const CANVAS_WIDTH = 1000; 5 | const CANVAS_HEIGHT = 1000; 6 | let k; 7 | let mNodeList = []; 8 | let mEdgeList = []; 9 | let mDxMap = {}; 10 | let mDyMap = {}; 11 | let mNodeMap = {}; 12 | export default function ForceDirected() { 13 | //generate nodes and edges 14 | for (let i = 0; i < 20; i++) { 15 | mNodeList.push(new Node(i)); 16 | } 17 | 18 | for (let i = 0; i < 20; i++) { 19 | let edgeCount = Math.random() * 8 + 1; 20 | for (let j = 0; j < edgeCount; j++) { 21 | let targetId = Math.floor(Math.random() * 20); 22 | let edge = new Edge(i, targetId); 23 | mEdgeList.push(edge); 24 | } 25 | } 26 | if (mNodeList && mEdgeList) { 27 | k = Math.sqrt(CANVAS_WIDTH * CANVAS_HEIGHT / mNodeList.length); 28 | } 29 | for (let i = 0; i < mNodeList.length; i++) { 30 | let node = mNodeList[i]; 31 | if (node) { 32 | mNodeMap[node.id] = node; 33 | } 34 | } 35 | 36 | //随机生成坐标. Generate coordinates randomly. 37 | let initialX, initialY, initialSize = 40.0; 38 | for (let i in mNodeList) { 39 | initialX = CANVAS_WIDTH * .5; 40 | initialY = CANVAS_HEIGHT * .5; 41 | mNodeList[i].x = initialX + initialSize * (Math.random() - .5); 42 | mNodeList[i].y = initialY + initialSize * (Math.random() - .5); 43 | } 44 | 45 | //迭代200次. Iterate 200 times. 46 | for (let i = 0; i < 200; i++) { 47 | calculateRepulsive(); 48 | calculateTraction(); 49 | updateCoordinates(); 50 | } 51 | console.log(JSON.stringify(new Result(mNodeList, mEdgeList))); 52 | } 53 | 54 | function Node(id = null) { 55 | this.id = id; 56 | this.x = 22; 57 | this.y = null; 58 | } 59 | function Edge(source = null, target = null) { 60 | this.source = source; 61 | this.target = target; 62 | } 63 | 64 | /** 65 | * 计算两个Node的斥力产生的单位位移。 66 | * Calculate the displacement generated by the repulsive force between two nodes.* 67 | */ 68 | function calculateRepulsive() { 69 | let ejectFactor = 6; 70 | let distX, distY, dist; 71 | for (let i = 0; i < mNodeList.length; i++) { 72 | mDxMap[mNodeList[i].id] = 0.0; 73 | mDyMap[mNodeList[i].id] = 0.0; 74 | for (let j = 0; j < mNodeList.length; j++) { 75 | if (i !== j) { 76 | distX = mNodeList[i].x - mNodeList[j].x; 77 | distY = mNodeList[i].y - mNodeList[j].y; 78 | dist = Math.sqrt(distX * distX + distY * distY); 79 | } 80 | if (dist < 30) { 81 | ejectFactor = 5; 82 | } 83 | if (dist > 0 && dist < 250) { 84 | let id = mNodeList[i].id; 85 | mDxMap[id] = mDxMap[id] + distX / dist * k * k / dist * ejectFactor; 86 | mDyMap[id] = mDyMap[id] + distY / dist * k * k / dist * ejectFactor; 87 | } 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * 计算Edge的引力对两端Node产生的引力。 94 | * Calculate the traction force generated by the edge acted on the two nodes of its two ends. 95 | */ 96 | function calculateTraction() { 97 | let condenseFactor = 3; 98 | let startNode, endNode; 99 | for (let e = 0; e < mEdgeList.length; e++) { 100 | let eStartID = mEdgeList[e].source; 101 | let eEndID = mEdgeList[e].target; 102 | startNode = mNodeMap[eStartID]; 103 | endNode = mNodeMap[eEndID]; 104 | if (!startNode) { 105 | console.log("Cannot find start node id: " + eStartID + ", please check it out."); 106 | return; 107 | } 108 | if (!endNode) { 109 | console.log("Cannot find end node id: " + eEndID + ", please check it out."); 110 | return; 111 | } 112 | let distX, distY, dist; 113 | distX = startNode.x - endNode.x; 114 | distY = startNode.y - endNode.y; 115 | dist = Math.sqrt(distX * distX + distY * distY); 116 | mDxMap[eStartID] = mDxMap[eStartID] - distX * dist / k * condenseFactor; 117 | mDyMap[eStartID] = mDyMap[eStartID] - distY * dist / k * condenseFactor; 118 | mDxMap[eEndID] = mDxMap[eEndID] + distX * dist / k * condenseFactor; 119 | mDyMap[eEndID] = mDyMap[eEndID] + distY * dist / k * condenseFactor; 120 | } 121 | } 122 | 123 | /** 124 | * 更新坐标。 125 | * update the coordinates. 126 | */ 127 | function updateCoordinates() { 128 | let maxt = 4, maxty = 3; //Additional coefficients. 129 | for (let v = 0; v < mNodeList.length; v++) { 130 | let node = mNodeList[v]; 131 | let dx = Math.floor(mDxMap[node.id]); 132 | let dy = Math.floor(mDyMap[node.id]); 133 | 134 | if (dx < -maxt) dx = -maxt; 135 | if (dx > maxt) dx = maxt; 136 | if (dy < -maxty) dy = -maxty; 137 | if (dy > maxty) dy = maxty; 138 | node.x = node.x + dx >= CANVAS_WIDTH || node.x + dx <= 0 ? node.x - dx : node.x + dx; 139 | node.y = node.y + dy >= CANVAS_HEIGHT || node.y + dy <= 0 ? node.y - dy : node.y + dy; 140 | } 141 | } 142 | 143 | function Result(nodes = null, links = null) { 144 | this.nodes = nodes; 145 | this.links = links; 146 | } -------------------------------------------------------------------------------- /java/data/DataSet1.java: -------------------------------------------------------------------------------- 1 | package github.ForceDirectLayout.java.data; 2 | 3 | 4 | public class DataSet1 { 5 | public static String dataSet = 6 | "0 1;" + 7 | "1 7;" + 8 | "1 4;" + 9 | "1 5;" + 10 | "1 43;" + 11 | "1 38;" + 12 | "1 9;" + 13 | "1 3;" + 14 | "1 68;" + 15 | "1 8;" + 16 | "1 22;" + 17 | "1 85;" + 18 | "1 87;" + 19 | "1 37;" + 20 | "1 41;" + 21 | "1 2;" + 22 | "1 12;" + 23 | "1 51;" + 24 | "1 76;" + 25 | "1 50;" + 26 | "2 4;" + 27 | "2 83;" + 28 | "2 54;" + 29 | "2 31;" + 30 | "2 80;" + 31 | "2 18;" + 32 | "2 61;" + 33 | "2 7;" + 34 | "2 98;" + 35 | "2 9;" + 36 | "2 3;" + 37 | "2 34;" + 38 | "2 41;" + 39 | "2 21;" + 40 | "3 10;" + 41 | "3 7;" + 42 | "3 4;" + 43 | "3 30;" + 44 | "3 47;" + 45 | "3 62;" + 46 | "3 29;" + 47 | "3 26;" + 48 | "3 13;" + 49 | "3 28;" + 50 | "3 40;" + 51 | "3 42;" + 52 | "3 9;" + 53 | "3 84;" + 54 | "3 49;" + 55 | "3 81;" + 56 | "3 12;" + 57 | "3 15;" + 58 | "3 67;" + 59 | "3 79;" + 60 | "3 92;" + 61 | "3 8;" + 62 | "3 53;" + 63 | "4 11;" + 64 | "4 7;" + 65 | "4 56;" + 66 | "4 5;" + 67 | "4 29;" + 68 | "4 13;" + 69 | "4 9;" + 70 | "4 50;" + 71 | "5 94;" + 72 | "5 43;" + 73 | "5 68;" + 74 | "5 93;" + 75 | "5 2;" + 76 | "6 90;" + 77 | "6 52;" + 78 | "6 48;" + 79 | "6 45;" + 80 | "6 95;" + 81 | "7 18;" + 82 | "7 11;" + 83 | "7 83;" + 84 | "7 56;" + 85 | "7 62;" + 86 | "7 87;" + 87 | "7 41;" + 88 | "7 54;" + 89 | "7 61;" + 90 | "7 9;" + 91 | "7 76;" + 92 | "7 50;" + 93 | "8 30;" + 94 | "8 29;" + 95 | "8 35;" + 96 | "8 15;" + 97 | "8 60;" + 98 | "8 62;" + 99 | "8 26;" + 100 | "8 13;" + 101 | "8 28;" + 102 | "8 42;" + 103 | "8 2;" + 104 | "8 71;" + 105 | "8 24;" + 106 | "8 16;" + 107 | "8 25;" + 108 | "8 23;" + 109 | "8 47;" + 110 | "8 72;" + 111 | "8 70;" + 112 | "8 12;" + 113 | "8 19;" + 114 | "9 18;" + 115 | "9 29;" + 116 | "9 41;" + 117 | "9 64;" + 118 | "9 13;" + 119 | "9 40;" + 120 | "9 54;" + 121 | "9 61;" + 122 | "9 81;" + 123 | "9 15;" + 124 | "9 79;" + 125 | "9 31;" + 126 | "9 53;" + 127 | "10 7;" + 128 | "10 4;" + 129 | "10 56;" + 130 | "10 29;" + 131 | "10 14;" + 132 | "10 33;" + 133 | "10 2;" + 134 | "10 1;" + 135 | "11 50;" + 136 | "11 56;" + 137 | "12 30;" + 138 | "12 29;" + 139 | "12 35;" + 140 | "12 23;" + 141 | "12 60;" + 142 | "12 47;" + 143 | "12 26;" + 144 | "12 28;" + 145 | "12 42;" + 146 | "12 72;" + 147 | "12 70;" + 148 | "12 78;" + 149 | "13 54;" + 150 | "13 18;" + 151 | "13 42;" + 152 | "13 15;" + 153 | "13 67;" + 154 | "13 29;" + 155 | "13 7;" + 156 | "13 2;" + 157 | "13 30;" + 158 | "13 1;" + 159 | "13 71;" + 160 | "13 26;" + 161 | "13 31;" + 162 | "13 19;" + 163 | "13 64;" + 164 | "13 16;" + 165 | "13 12;" + 166 | "13 28;" + 167 | "13 62;" + 168 | "14 29;" + 169 | "14 7;" + 170 | "14 2;" + 171 | "14 4;" + 172 | "14 1;" + 173 | "14 3;" + 174 | "14 33;" + 175 | "14 56;" + 176 | "15 18;" + 177 | "15 7;" + 178 | "15 4;" + 179 | "15 30;" + 180 | "15 47;" + 181 | "15 62;" + 182 | "15 98;" + 183 | "15 26;" + 184 | "15 41;" + 185 | "15 64;" + 186 | "15 28;" + 187 | "15 42;" + 188 | "15 2;" + 189 | "15 12;" + 190 | "15 67;" + 191 | "15 1;" + 192 | "15 31;" + 193 | "16 30;" + 194 | "16 71;" + 195 | "16 12;" + 196 | "16 24;" + 197 | "16 25;" + 198 | "16 29;" + 199 | "16 26;" + 200 | "16 19;" + 201 | "16 27;" + 202 | "16 28;" + 203 | "17 30;" + 204 | "17 29;" + 205 | "17 35;" + 206 | "17 42;" + 207 | "17 23;" + 208 | "17 70;" + 209 | "17 36;" + 210 | "18 4;" + 211 | "18 41;" + 212 | "18 64;" + 213 | "18 54;" + 214 | "18 61;" + 215 | "18 76;" + 216 | "18 1;" + 217 | "18 31;" + 218 | "19 3;" + 219 | "19 26;" + 220 | "19 24;" + 221 | "19 25;" + 222 | "19 29;" + 223 | "19 70;" + 224 | "19 27;" + 225 | "19 35;" + 226 | "19 28;" + 227 | "19 30;" + 228 | "19 71;" + 229 | "20 63;" + 230 | "20 36;" + 231 | "21 83;" + 232 | "21 98;" + 233 | "21 80;" + 234 | "21 34;" + 235 | "21 41;" + 236 | "21 61;" + 237 | "22 2;" + 238 | "22 51;" + 239 | "22 5;" + 240 | "22 38;" + 241 | "23 30;" + 242 | "23 29;" + 243 | "23 35;" + 244 | "23 42;" + 245 | "23 63;" + 246 | "23 70;" + 247 | "23 36;" + 248 | "24 25;" + 249 | "24 29;" + 250 | "24 71;" + 251 | "24 26;" + 252 | "24 27;" + 253 | "24 23;" + 254 | "24 12;" + 255 | "24 28;" + 256 | "25 35;" + 257 | "25 30;" + 258 | "25 71;" + 259 | "25 3;" + 260 | "25 26;" + 261 | "25 27;" + 262 | "25 12;" + 263 | "25 28;" + 264 | "25 62;" + 265 | "26 42;" + 266 | "26 9;" + 267 | "26 2;" + 268 | "26 72;" + 269 | "26 30;" + 270 | "26 71;" + 271 | "26 62;" + 272 | "26 67;" + 273 | "26 29;" + 274 | "26 35;" + 275 | "26 1;" + 276 | "26 28;" + 277 | "27 28;" + 278 | "27 8;" + 279 | "27 71;" + 280 | "28 30;" + 281 | "28 62;" + 282 | "28 29;" + 283 | "28 42;" + 284 | "28 72;" + 285 | "28 71;" + 286 | "28 67;" + 287 | "28 1;" + 288 | "29 42;" + 289 | "29 2;" + 290 | "30 29;" + 291 | "30 35;" + 292 | "30 60;" + 293 | "30 47;" + 294 | "30 42;" + 295 | "30 72;" + 296 | "30 71;" + 297 | "30 70;" + 298 | "30 82;" + 299 | "30 1;" + 300 | "31 41;" + 301 | "32 97;" + 302 | "32 5;" + 303 | "32 54;" + 304 | "32 73;" + 305 | "32 2;" + 306 | "32 1;" + 307 | "34 83;" + 308 | "34 98;" + 309 | "34 41;" + 310 | "34 54;" + 311 | "34 80;" + 312 | "35 42;" + 313 | "35 78;" + 314 | "35 63;" + 315 | "35 70;" + 316 | "35 36;" + 317 | "36 63;" + 318 | "36 8;" + 319 | "36 70;" + 320 | "36 82;" + 321 | "37 7;" + 322 | "37 4;" + 323 | "37 2;" + 324 | "37 3;" + 325 | "37 51;" + 326 | "38 87;" + 327 | "38 9;" + 328 | "38 29;" + 329 | "38 7;" + 330 | "38 2;" + 331 | "38 76;" + 332 | "38 3;" + 333 | "38 51;" + 334 | "40 29;" + 335 | "40 7;" + 336 | "40 2;" + 337 | "40 79;" + 338 | "40 1;" + 339 | "40 49;" + 340 | "40 81;" + 341 | "40 12;" + 342 | "40 53;" + 343 | "41 83;" + 344 | "41 43;" + 345 | "41 98;" + 346 | "41 54;" + 347 | "41 80;" + 348 | "41 61;" + 349 | "42 60;" + 350 | "42 70;" + 351 | "42 78;" + 352 | "43 83;" + 353 | "43 68;" + 354 | "43 61;" + 355 | "43 2;" + 356 | "43 12;" + 357 | "44 2;" + 358 | "44 1;" + 359 | "44 41;" + 360 | "45 52;" + 361 | "45 48;" + 362 | "45 95;" + 363 | "46 2;" + 364 | "46 3;" + 365 | "46 94;" + 366 | "46 57;" + 367 | "46 29;" + 368 | "46 35;" + 369 | "46 58;" + 370 | "47 60;" + 371 | "47 42;" + 372 | "47 2;" + 373 | "47 72;" + 374 | "47 49;" + 375 | "47 82;" + 376 | "47 1;" + 377 | "48 52;" + 378 | "48 95;" + 379 | "49 60;" + 380 | "49 30;" + 381 | "49 9;" + 382 | "49 2;" + 383 | "49 72;" + 384 | "49 81;" + 385 | "49 12;" + 386 | "49 79;" + 387 | "49 1;" + 388 | "49 53;" + 389 | "50 2;" + 390 | "50 56;" + 391 | "51 30;" + 392 | "51 47;" + 393 | "51 12;" + 394 | "53 7;" + 395 | "53 2;" + 396 | "53 81;" + 397 | "53 12;" + 398 | "53 79;" + 399 | "53 1;" + 400 | "54 80;" + 401 | "54 61;" + 402 | "54 74;" + 403 | "55 17;" + 404 | "55 2;" + 405 | "55 3;" + 406 | "55 1;" + 407 | "56 2;" + 408 | "56 1;" + 409 | "57 3;" + 410 | "57 2;" + 411 | "57 58;" + 412 | "58 60;" + 413 | "58 86;" + 414 | "58 91;" + 415 | "58 35;" + 416 | "58 42;" + 417 | "58 2;" + 418 | "58 3;" + 419 | "58 12;" + 420 | "58 78;" + 421 | "59 94;" + 422 | "59 17;" + 423 | "59 99;" + 424 | "59 69;" + 425 | "60 78;" + 426 | "61 4;" + 427 | "61 64;" + 428 | "61 80;" + 429 | "61 1;" + 430 | "61 98;" + 431 | "61 74;" + 432 | "62 71;" + 433 | "62 64;" + 434 | "62 16;" + 435 | "63 30;" + 436 | "63 60;" + 437 | "63 42;" + 438 | "63 70;" + 439 | "63 12;" + 440 | "64 83;" + 441 | "64 2;" + 442 | "64 31;" + 443 | "65 2;" + 444 | "65 1;" + 445 | "65 89;" + 446 | "65 3;" + 447 | "65 12;" + 448 | "65 75;" + 449 | "67 9;" + 450 | "67 29;" + 451 | "67 2;" + 452 | "67 8;" + 453 | "67 1;" + 454 | "67 30;" + 455 | "67 71;" + 456 | "69 30;" + 457 | "69 94;" + 458 | "69 42;" + 459 | "69 17;" + 460 | "69 12;" + 461 | "69 99;" + 462 | "71 3;" + 463 | "72 60;" + 464 | "72 62;" + 465 | "72 29;" + 466 | "72 35;" + 467 | "72 42;" + 468 | "72 3;" + 469 | "72 82;" + 470 | "72 1;" + 471 | "72 16;" + 472 | "73 7;" + 473 | "73 5;" + 474 | "73 2;" + 475 | "73 1;" + 476 | "74 41;" + 477 | "74 2;" + 478 | "75 2;" + 479 | "75 3;" + 480 | "75 12;" + 481 | "75 1;" + 482 | "75 89;" + 483 | "76 87;" + 484 | "76 2;" + 485 | "78 30;" + 486 | "78 23;" + 487 | "78 17;" + 488 | "78 8;" + 489 | "78 70;" + 490 | "79 18;" + 491 | "79 29;" + 492 | "79 7;" + 493 | "79 2;" + 494 | "79 4;" + 495 | "79 1;" + 496 | "79 31;" + 497 | "79 81;" + 498 | "80 98;" + 499 | "81 2;" + 500 | "81 1;" + 501 | "81 12;" + 502 | "82 43;" + 503 | "82 35;" + 504 | "82 3;" + 505 | "82 8;" + 506 | "82 60;" + 507 | "82 42;" + 508 | "82 2;" + 509 | "82 12;" + 510 | "83 54;" + 511 | "83 31;" + 512 | "83 80;" + 513 | "84 9;" + 514 | "84 2;" + 515 | "84 92;" + 516 | "86 17;" + 517 | "86 91;" + 518 | "86 88;" + 519 | "88 91;" + 520 | "88 17;" + 521 | "89 2;" + 522 | "89 3;" + 523 | "89 12;" + 524 | "89 1;" + 525 | "91 94;" + 526 | "91 17;" + 527 | "92 9;" + 528 | "92 2;" + 529 | "93 22;" + 530 | "93 1;" + 531 | "99 94;" + 532 | "99 35;" + 533 | "99 17;" + 534 | "99 70;"; 535 | } 536 | --------------------------------------------------------------------------------