D3.js實(shí)現(xiàn)力向?qū)D的繪制教程詳解
力向?qū)D是繪圖的一種算法,實(shí)現(xiàn)了用以模擬粒子物理運(yùn)動(dòng)的 velocity Verlet 數(shù)值積分器。仿真思路如下: 它假設(shè)任意單位時(shí)間步長 Δt = 1,所有的粒子的單位質(zhì)量常量 m = 1。作用在每個(gè)粒子上的合力 F 相當(dāng)于在單位時(shí)間 Δt 內(nèi)的恒定加速度 a。并且可以簡單的通過為每個(gè)粒子添加速度并計(jì)算粒子的位置來模擬仿真。
在二維或三維空間里配置節(jié)點(diǎn)。節(jié)點(diǎn)之間用線連接,稱為連線。各連線的長度幾乎相等,且盡可能不相交。節(jié)點(diǎn)和連線都被施加了力的作用。力的大小是根據(jù)節(jié)點(diǎn)和連線的相對(duì)位置計(jì)算的。根據(jù)力的作用來計(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ū)域的碰撞檢測力模型.
- collide.radius - 設(shè)置碰撞半徑.
- collide.strength - 設(shè)置碰撞檢測力模型的強(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))
接下來我們繪制一下文章開頭的那張關(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)文章
javascript實(shí)現(xiàn)顏色漸變的方法
這篇文章介紹了javascript實(shí)現(xiàn)顏色漸變的方法,有需要的朋友可以參考一下2013-10-10微信小程序中target和currentTarget的區(qū)別小結(jié)
這篇文章主要給大家介紹了關(guān)于微信小程序中target和currentTarget區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11javascript下查找父節(jié)點(diǎn)的簡單方法
javascript下查找父節(jié)點(diǎn)的簡單方法...2007-08-08前端實(shí)現(xiàn)Excel文件導(dǎo)出功能的完整代碼解析(vue實(shí)現(xiàn)excel文件導(dǎo)出)
在Vue中實(shí)現(xiàn)導(dǎo)出Excel文件有多種方式,可以通過前端實(shí)現(xiàn),也可以通過前后端配合實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于前端實(shí)現(xiàn)Excel文件導(dǎo)出功能(vue實(shí)現(xiàn)excel文件導(dǎo)出)的相關(guān)資料,需要的朋友可以參考下2024-05-05JavaScript如何一次性展示幾萬條數(shù)據(jù)
本文主要介紹了JavaScript一次性展示幾萬條數(shù)據(jù)的實(shí)現(xiàn)方法。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-03-03面試官常問之說說js中var、let、const的區(qū)別
var、let和const都是JavaScript中用來聲明變量的關(guān)鍵字,并且let和 const關(guān)鍵字是在 ES6 中才新增的,下面這篇文章主要給大家介紹了關(guān)于var、let、const區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-03-03