D3.js實現(xiàn)力向?qū)D的繪制教程詳解
力向?qū)D是繪圖的一種算法,實現(xiàn)了用以模擬粒子物理運動的 velocity Verlet 數(shù)值積分器。仿真思路如下: 它假設(shè)任意單位時間步長 Δt = 1,所有的粒子的單位質(zhì)量常量 m = 1。作用在每個粒子上的合力 F 相當(dāng)于在單位時間 Δt 內(nèi)的恒定加速度 a。并且可以簡單的通過為每個粒子添加速度并計算粒子的位置來模擬仿真。
在二維或三維空間里配置節(jié)點。節(jié)點之間用線連接,稱為連線。各連線的長度幾乎相等,且盡可能不相交。節(jié)點和連線都被施加了力的作用。力的大小是根據(jù)節(jié)點和連線的相對位置計算的。根據(jù)力的作用來計算節(jié)點和連線的運動軌跡,并不斷降低它們的能量,最終達到一種能量很低的穩(wěn)定狀態(tài)。

力
D3 Force 是一種模擬物理運動原理的繪圖算法,一開始給所有節(jié)點設(shè)置任意的初始值配置,接著根據(jù)配置的屬性,讓每個節(jié)點按屬性去運動——這就是每個節(jié)點之間的力。
力又分為斥力和引力,每個節(jié)點之間的力就是斥力,而整個圖形又存在引力,就像人可以在地面上起跳,但是由于地心引力,我們不會跳的很高,最終也都會落回地面。節(jié)點在各種力的交互作用下,碰撞聚攏,逐漸收攏到一個穩(wěn)定的位置,可以通過alpha屬性去設(shè)置整個過程的速度,還可以設(shè)置摩擦力velocityDecay去調(diào)整速度。
五種力
D3 一共給我們提供了五種力點擊查看Demo:
向心力
- d3.forceCenter([x, y]) - 創(chuàng)建一個中心作用力.
- center.x - 設(shè)置中心作用力的 x -坐標(biāo).
- center.y - 設(shè)置中心作用力的 y -坐標(biāo).
向心力可以將所有的節(jié)點的中心統(tǒng)一整體的向指定的位置 〈x,y〉 移動。這種力強制修改每個節(jié)點的位置,但是不會修改速度,因為修改速度會造成節(jié)點在期望的位置附近抖動。這種力可以輔助保持所有的節(jié)點在視口中心。

碰撞力
節(jié)點之間相互碰撞的力,這個斥力會防止節(jié)點重合,可以使用 strength設(shè)置斥力的強弱。
- d3.forceCollide - 創(chuàng)建一個圓形區(qū)域的碰撞檢測力模型.
- collide.radius - 設(shè)置碰撞半徑.
- collide.strength - 設(shè)置碰撞檢測力模型的強度.
- collide.iterations - 設(shè)置迭代次數(shù).

連接力(彈簧力)
使用d3.forceLink 將兩個節(jié)點添加連線到一起之后,就可以設(shè)置連接力了,它會根據(jù)兩節(jié)點之間的距離,拉近或推遠節(jié)點,力的強弱和兩節(jié)點間的距離成正比,就像彈簧一樣,所以也叫彈簧力。
- d3.forceLink - 創(chuàng)建一個
link(彈簧) 作用力. - link.links - 設(shè)置彈簧作用力的邊.
- link.id - 設(shè)置邊元素中節(jié)點的查找方式是索引還是
id字符串. - link.distance - 設(shè)置
link的距離. - link.strength - 設(shè)置
link的強度. - link.iterations - 設(shè)置迭代次數(shù).

電荷力
模擬所有節(jié)點之間的相互作用力,如果是正值,則相互吸引,如果是負(fù)值,則相互排斥。這樣就可以模擬電荷的吸引力,力的強弱也和節(jié)點間的距離有關(guān)。
- d3.forceManyBody - 創(chuàng)建一個電荷作用力模型.
- manyBody.strength - 設(shè)置電荷力模型的強度.
- manyBody.theta - 設(shè)置
Barnes–Hut算法的精度. - manyBody.distanceMin - 限制節(jié)點之間的最小距離.
- manyBody.distanceMax - 限制節(jié)點之間的最大距離.

徑向力
設(shè)定一個圓,這樣所有的節(jié)點都會有一個指向圓心的力,這樣每個節(jié)點都會集中到圓上。
- d3.forceRadial - 創(chuàng)建一個環(huán)形布局的作用力.
- radial.strength - 設(shè)置力強度.
- radial.radius - 設(shè)置目標(biāo)半徑.
- radial.x - 設(shè)置環(huán)形作用力的目標(biāo)中心 x -坐標(biāo).
- radial.y - 設(shè)置環(huán)形作用力的目標(biāo)中心 y -坐標(biāo).

五種力是可以疊加使用的!
力向?qū)D
在創(chuàng)建力學(xué)導(dǎo)圖時,我們需要先創(chuàng)建一個新的力學(xué)模型d3.forceSimulation,并指定節(jié)點nodes。
1.d3.forceSimulation - 創(chuàng)建一個新的力學(xué)仿真.
2.simulation.restart - 重新啟動仿真的定時器.
3.simulation.stop - 停止仿真的定時器.
4.simulation.tick - 進行一步仿真模擬.
5.simulation.nodes - 設(shè)置仿真的節(jié)點.
每個 node 必須是一個對象類型,下面的幾個屬性將會被仿真系統(tǒng)添加:
index- 節(jié)點在 nodes 數(shù)組中的索引x- 節(jié)點當(dāng)前的 x-坐標(biāo)y- 節(jié)點當(dāng)前的 y-坐標(biāo)vx- 節(jié)點當(dāng)前的 x-方向速度vy- 節(jié)點當(dāng)前的 y-方向速度
5.simulation.alpha - 設(shè)置當(dāng)前的 alpha 值.
6.simulation.alphaMin - 設(shè)置最小 alpha 閾值.
7.simulation.alphaDecay - 設(shè)置 alpha 衰減率.
為0的話,永遠都不會停
8.simulation.alphaTarget - 設(shè)置目標(biāo) alpha 值.
9.simulation.velocityDecay - 設(shè)置速度衰減率.
10.simulation.force - 添加或移除一個力模型.
11.simulation.find - 根據(jù)指定的位置找出最近的節(jié)點.
12.simulation.on - 添加或移除事件監(jiān)聽器.
tick- 仿真內(nèi)部定時器每次tick之后。end- 當(dāng) alpha < alphaMin 時仿真內(nèi)部定時器停止。
const simulation = d3.forceSimulation(nodes)
// 連接力
.force('link', d3.forceLink())
// 在 y軸方向上施加一個力
.force('y', d3.forceY().strength(0.025))
// 電荷力
.force('charge', d3.forceManyBody())
// 碰撞力
.force('collision', d3.forceCollide().radius(d => 4))
// 向心力
.force('center', d3.forceCenter(width / 2, height / 2))
接下來我們繪制一下文章開頭的那張關(guān)系圖點擊查看Demo:
創(chuàng)建模擬數(shù)據(jù)
const nodes = [
{name: "張三"}
...
]
const links = [
{ source: 0, target: 1, relation: "關(guān)系1"}
...
]創(chuàng)建力模型
let simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).distance(100)) // 連接力
繪制節(jié)點和連線
// 畫線
function drawLine() {
let lines = svg.append("g")
.selectAll(".force-line")
.data(links)
.enter()
.append("line")
.attr("class", "line")
.attr("stroke", "#999")
.attr("stroke-width", "1px");
return lines;
}
let lines = drawLine();
// 畫節(jié)點節(jié)點盒子
function drawCircle() {
let nodeGroups = svg.append("g")
.attr("class", "nodes-box")
.selectAll(".force-node")
.data(nodes)
.enter()
.append("g")
.attr("class", "force-circle")
nodeGroups.append("circle")
.attr("class", "force-circle")
.attr("r", 20)
.style("fill",(d, i) => color(i));
nodeGroups.append("text")
.attr("class", "force-text")
.attr("dy", ".33em")
.attr("font-size", "12px")
.attr("text-anchor", "middle")
.style("fill", "#eee")
.text(d => d.name);
return nodeGroups;
}
let nodesCircle = drawCircle();
到這一步呀,就只能看到畫布的左上角,原點位置 有circle圖形,因為力學(xué)模型,是動態(tài)計算節(jié)點和連線的位置,所以我們需要動態(tài)的去更新它們的位置<x, y>,此時我們需要監(jiān)聽的就是tick.

監(jiān)聽 tick
let simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).distance(100));
.on("tick", ticked);
function ticked()
lines
.attr("x1", (d) => d.source.x)
.attr("y1", (d) => d.source.y)
.attr("x2", (d) => d.target.x)
.attr("y2", (d) => d.target.y);
// 這里就不適合 去改變circle的圓心位置了,因為有文字存在,改變整個circleGroup的transform
nodesCircle.attr("transform", function (d) {
// d.fx=d.x;d.fy=d.y; 固定位置
return "translate(" + d.x + ", " + d.y + ")";
});
}
我們可以添加一個向心力,讓整個圖形出現(xiàn)在畫布中心。
添加向心力
let simulation = d3.forceSimulation(nodes)
.force("center", d3.forceCenter(width / 2, height / 2)) // 用指定的x坐標(biāo)和y坐標(biāo)創(chuàng)建一個居中力
.force("link", d3.forceLink(links).distance(100)) //
.on("tick", ticked);

到這里,我們可以發(fā)現(xiàn),節(jié)點出現(xiàn)了重合的現(xiàn)象,我們可以給節(jié)點添加一個碰撞力,讓它們分開。
添加碰撞力
let simulation = d3.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-200)) // 電荷力 相互之間的作用力
.force("center", d3.forceCenter(width / 2, height / 2)) // 用指定的x坐標(biāo)和y坐標(biāo)創(chuàng)建一個居中力
.force("link", d3.forceLink(links).distance(100)) //
.on("tick", ticked);

添加拖拽效果
d3.drag后面再詳細(xì)介紹,本章就不深入了。
let nodeGroups = svg.append("g")
...
.call(
d3.drag().on("start", started).on("drag", dragged).on("end", ended)
);
// 拖拽
function started(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
function ended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
整個拖拽的過程中:
- 連接力:拖拽任意節(jié)點,其余節(jié)點都會同向運動
- 碰撞力:在拖拽的過程中,各節(jié)點之間不會重合
- 向心力:拖拽不能拖到任意位置,由于向心力的存在,最后還是會向中心靠攏
以上就是D3.js實現(xiàn)力向?qū)D的繪制教程詳解的詳細(xì)內(nèi)容,更多關(guān)于D3.js力向?qū)D的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS實現(xiàn)點擊上移下移LI行數(shù)據(jù)的方法
這篇文章主要介紹了JS實現(xiàn)點擊上移下移LI行數(shù)據(jù)的方法,涉及javascript針對LI列表動態(tài)排序的相關(guān)實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-08-08

