# 力導圖
模擬粒子物理運動,甚至可以用來作為一個基本的物理引擎。目前數據展示方面主要用于關系鏈
如:天眼查 馬云公司關系

## 需求
已知有節點['A','B','C','D','AB','AC','BD']
其中含相同字母的連接:
```text
A - AB
B - AB
A - AC
C - AC
B - BD
D - BD
```
## 分析
首先建議閱讀[力導圖-中文](https://github.com/xswei/d3-force#simulation_nodes)
先說說d3-force。
力模擬器d3.forceSimulation,通過指定的node和各種力來模擬出node的位置。
力的模型有:
forceCenter:中心力
forceLink:彈簧模型
forceCollide:用于碰撞檢測式力
forceManyBody:電荷力
forceX和forceY:定位力模型可以將節點沿著指定的維度進行排列
forceRadial:環形力的參考位置是一個閉合的環
接下來需要構建數據:
```javascript
var nodes_data = [{name:'A'},
{name:'B'},{name:'C'},{name:'D'},
{name:'AB'},{name:'AC'},{name:'BD'}];
var edges_data = [
{source:0,target:4 },
{source:1,target:4 },
{source:0,target:5 },
{source:2,target:5 },
{source:1,target:6 },
{source:3,target:6 }
];
```
分析:
1. 為了使圖居中所以使用forceCenter力設置中心位置
2. 為了使用節點之間有拖拽彈性所以使用forceLink
3. 為了使節點之間不重疊使用電荷力forceManyBody
### 解析
1. 力導圖和其他布局有所區別:其他布局是一次計算好的,而力導圖是一個模擬力作用的動態變化。
2. 重點是我們需要配置好force后,設置回調tick,當某點的位置發生變化,會使力場發生變化,此時的force會不斷的回調tick,而我們需要在tick中去更新如圓,連線,文字位置等。
## 繪制
``` javascript
//創建svg
var svg = d3.select('#root')
.append('svg')
.attr('width', 600)
.attr('height', 600)
.style("background-color","rgb(142, 137, 137)");
var margin=[100,100,100,100]
//構建顏色比例尺
var color = d3.scaleOrdinal(d3.schemeCategory20)
//模擬數據:
var nodes_data = [{name:'A'},
{name:'B'},{name:'C'},{name:'D'},
{name:'AB'},{name:'AC'},{name:'BD'}];
var edges_data = [
{source:0,target:4 },
{source:1,target:4 },
{source:0,target:5 },
{source:2,target:5 },
{source:1,target:6 },
{source:3,target:6 }
];
var simulation = d3.forceSimulation(nodes_data)
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink(edges_data).distance(20).strength(1))
.force("center", d3.forceCenter(300, 300))
.on('tick', tick)
//繪制links
var link = svg.selectAll("g.link")
.data(edges_data)
.enter()
.append('g')
.attr("class", 'link')
.append('line')
.attr('stroke', '#000')
.attr('stroke-width', 1)
.attr('x1', function (d) { return d.source.x })
.attr('y1', function (d) { return d.source.y })
.attr('x2', function (d) { return d.target.x })
.attr('y2', function (d) { return d.target.y })
var dragged = d3.drag()
.container(document.getElementsByTagName("svg")[0])
.on('start', function (d) {
if (!d3.event.active) simulation.alphaTarget(0.6).restart();
d.fx = d.x;
d.fy = d.y;
}) //mousedown
.on('drag', function (d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}) //mousemove
.on('end', function (d) {
console.log(d3.event.subject)
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}) //mousedown
//繪制節點
var nodes = svg.selectAll("g.nodes")
.data(nodes_data)
.enter()
.append("g")
.attr("class", "nodes")
.call(dragged)
var node = nodes.append("circle")
.attr("cx", function (d) { return d.x })
.attr("cy", function (d) { return d.y })
.attr("r", 20)
.attr('fill', function (d, i) { return color(i) })
var nodeT = nodes.append('text')
.attr('x',function (d) { return d.x })
.attr("y", function (d) { return d.y })
.text(function(d){console.log(d); return d.name})
//當力模擬完成后修改顯示狀態
function tick() {
link.attr('x1', function (d) { return d.source.x })
.attr('y1', function (d) { return d.source.y })
.attr('x2', function (d) { return d.target.x })
.attr('y2', function (d) { return d.target.y })
node.attr("cx", function (d) { return d.x })
.attr("cy", function (d) { return d.y })
nodeT.attr('x',function (d) {
var textL = d.name.length;
return d.x-((textL/2)*10)
})
.attr("y", function (d) { return d.y+5 })
}
```
## 實例
[force0](https://doter1995.github.io/d3-start-course/force/force-0.html)
關于文字居中在圓心,x通過拿到文本長度計算偏移。缺點是文字出現長度為0,是比較尷尬。
[force1](https://doter1995.github.io/d3-start-course/force/force-1.html)
關于文字居中在圓心,這個使用text-anchor的middle即可自動處理偏移。所以只需要為y添加偏移即可。
