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

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

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

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

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

徑向力
設(shè)定一個(gè)圓,這樣所有的節(jié)點(diǎn)都會(huì)有一個(gè)指向圓心的力,這樣每個(gè)節(jié)點(diǎn)都會(huì)集中到圓上。
- d3.forceRadial - 創(chuàng)建一個(gè)環(huán)形布局的作用力.
- radial.strength - 設(shè)置力強(qiáng)度.
- 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)圖時(shí),我們需要先創(chuàng)建一個(gè)新的力學(xué)模型d3.forceSimulation,并指定節(jié)點(diǎn)nodes。
1.d3.forceSimulation - 創(chuàng)建一個(gè)新的力學(xué)仿真.
2.simulation.restart - 重新啟動(dòng)仿真的定時(shí)器.
3.simulation.stop - 停止仿真的定時(shí)器.
4.simulation.tick - 進(jìn)行一步仿真模擬.
5.simulation.nodes - 設(shè)置仿真的節(jié)點(diǎn).
每個(gè) node 必須是一個(gè)對(duì)象類型,下面的幾個(gè)屬性將會(huì)被仿真系統(tǒng)添加:
index- 節(jié)點(diǎn)在 nodes 數(shù)組中的索引x- 節(jié)點(diǎn)當(dāng)前的 x-坐標(biāo)y- 節(jié)點(diǎn)當(dāng)前的 y-坐標(biāo)vx- 節(jié)點(diǎn)當(dāng)前的 x-方向速度vy- 節(jié)點(diǎn)當(dāng)前的 y-方向速度
5.simulation.alpha - 設(shè)置當(dāng)前的 alpha 值.
6.simulation.alphaMin - 設(shè)置最小 alpha 閾值.
7.simulation.alphaDecay - 設(shè)置 alpha 衰減率.
為0的話,永遠(yuǎn)都不會(huì)停
8.simulation.alphaTarget - 設(shè)置目標(biāo) alpha 值.
9.simulation.velocityDecay - 設(shè)置速度衰減率.
10.simulation.force - 添加或移除一個(gè)力模型.
11.simulation.find - 根據(jù)指定的位置找出最近的節(jié)點(diǎn).
12.simulation.on - 添加或移除事件監(jiān)聽器.
tick- 仿真內(nèi)部定時(shí)器每次tick之后。end- 當(dāng) alpha < alphaMin 時(shí)仿真內(nèi)部定時(shí)器停止。
const simulation = d3.forceSimulation(nodes)
// 連接力
.force('link', d3.forceLink())
// 在 y軸方向上施加一個(gè)力
.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))
接下來(lái)我們繪制一下文章開頭的那張關(guān)系圖點(diǎ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é)點(diǎn)和連線
// 畫線
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é)點(diǎn)節(jié)點(diǎn)盒子
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();
到這一步呀,就只能看到畫布的左上角,原點(diǎn)位置 有circle圖形,因?yàn)榱W(xué)模型,是動(dòng)態(tài)計(jì)算節(jié)點(diǎn)和連線的位置,所以我們需要?jiǎng)討B(tài)的去更新它們的位置<x, y>,此時(shí)我們需要監(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的圓心位置了,因?yàn)橛形淖执嬖冢淖冋麄€(gè)circleGroup的transform
nodesCircle.attr("transform", function (d) {
// d.fx=d.x;d.fy=d.y; 固定位置
return "translate(" + d.x + ", " + d.y + ")";
});
}
我們可以添加一個(gè)向心力,讓整個(gè)圖形出現(xiàn)在畫布中心。
添加向心力
let simulation = d3.forceSimulation(nodes)
.force("center", d3.forceCenter(width / 2, height / 2)) // 用指定的x坐標(biāo)和y坐標(biāo)創(chuàng)建一個(gè)居中力
.force("link", d3.forceLink(links).distance(100)) //
.on("tick", ticked);

到這里,我們可以發(fā)現(xiàn),節(jié)點(diǎn)出現(xiàn)了重合的現(xiàn)象,我們可以給節(jié)點(diǎn)添加一個(gè)碰撞力,讓它們分開。
添加碰撞力
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)建一個(gè)居中力
.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;
}
整個(gè)拖拽的過程中:
- 連接力:拖拽任意節(jié)點(diǎn),其余節(jié)點(diǎn)都會(huì)同向運(yùn)動(dòng)
- 碰撞力:在拖拽的過程中,各節(jié)點(diǎn)之間不會(huì)重合
- 向心力:拖拽不能拖到任意位置,由于向心力的存在,最后還是會(huì)向中心靠攏
以上就是D3.js實(shí)現(xiàn)力向?qū)D的繪制教程詳解的詳細(xì)內(nèi)容,更多關(guān)于D3.js力向?qū)D的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS實(shí)現(xiàn)點(diǎn)擊上移下移LI行數(shù)據(jù)的方法
這篇文章主要介紹了JS實(shí)現(xiàn)點(diǎn)擊上移下移LI行數(shù)據(jù)的方法,涉及javascript針對(duì)LI列表動(dòng)態(tài)排序的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08
常用的js驗(yàn)證和數(shù)據(jù)處理總結(jié)
遇到需要對(duì)數(shù)據(jù)及表單驗(yàn)證的,我相信大家都像我一樣,喜歡在網(wǎng)上找相關(guān)的方法,因?yàn)樽约簩懙脑挘潜容^耗時(shí)的。今天就給大家分享一下,自己在工作中總結(jié)的一些常用的js。2016-08-08
js獲取瀏覽器地址(獲取第1個(gè)斜杠后的內(nèi)容)
這篇文章主要給大家介紹了關(guān)于js獲取瀏覽器地址(獲取第1個(gè)斜杠后的內(nèi)容)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用js具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09

