摘要:通過力導向算法計算位置,繪制出對應的力導向圖,這樣的分配是最佳位置的分布圖。力導向算法是根據自然界中電子直接互相作用的原理來實現的,自然界中。
前言
說到力導向可能很多小伙伴都只是會使用,不知道其中的實現原理,今天,我們一起來自己實現一套力導向算法,然后做一些技術相關的延伸。發散下思維。
什么是力導向算法?根據百科的介紹:力導向算法是指通過對每個節點的計算,算出引力和排斥力綜合的合力,再由此合力來移動節點的位置。
通過力導向算法計算位置,繪制出對應的力導向圖,這樣的分配是最佳位置的分布圖。echarts和d3js里面也有力導向布局圖。首先來看一下力導向圖。
力導向算法是根據自然界中電子直接互相作用的原理來實現的,自然界中。兩個電子靠的太近會產生斥力,隔的太遠會產生引力,這樣保持一個平衡狀態,最終達到維持物體的形態的目的,這里就涉及到了一個庫侖定律(百科:是靜止點電荷相互作用力的規律。1785年法國科學家C,-A.de庫倫由實驗得出,真空中兩個靜止的點電荷之間的相互作用力同它們的電荷量的乘積成正比,與它們的距離的二次方成反比,作用力的方向在它們的連線上,同名電荷相斥,異名電荷相吸),這里就涉及到一個庫倫公式。,如果假設電子q=1,那么 F=k/(r^2) * e(e為從q1到q2方向的矢徑;k為庫侖常數(靜電力常量))。那這里的F可以假設為某個方向的瞬間速度,e正好代表正負方向,有的力導向圖算法中加入了彈簧力,讓e有了緩動效果,但是,這里我們就不加入彈簧力了,主要是研究這個庫倫公式公式,如果進一步簡化,我們可以把F看做成一次函數的變化,這樣盡可能的簡化我們的代碼。復雜的問題簡單化,再慢慢深入。最終理解其原理。
實現邏輯如果要用代碼去實現簡化后的力導向圖的布局,我們需要幾個步驟。
設置點數據nodes, 鏈接數據links。
對點進行隨機定位。
渲染視圖
執行力算法計算位置,渲染視圖
重復執行4操作N次,得到想要的力導向圖形。在執行力算法的時候,這里我們把庫倫公式簡化成了一次函數,所以,要么減一個數,要么加一個數去改變點的坐標。理解起來就很容易了,當然,實際上我們應該加上電子作用力(庫倫公式)和彈簧力(胡克定律),讓力導向的效果更接近自然界的作用結果。
代碼實現原理圖:
設置數據
/** * @desc 模擬數據 */ function getData(num, exLink) { const data = { nodes: new Array(num).fill(1), links: [] }; data.nodes = data.nodes.map((d, id) => { return { id, name: d, position: [0, 0], childs: [] } }); data.nodes.forEach((d, i) => { // 都和0相連 if (d.id !== 0) { data.links.push({ source: 0, target: d.id, sourceNode: data.nodes[0], targetNode: d }); } }); // 隨機抽取其中2個相連 const randomLink = () => { data.nodes.sort(() => 0.5 - Math.random()); data.links.push({ source: data.nodes[0].id, target: data.nodes[1].id, sourceNode: data.nodes[0], targetNode: data.nodes[1] }); } for (let i = 0; i < exLink; i++) { randomLink(); }; // 添加數據。childs const obj = {}; data.nodes.forEach(d => { if (!obj[d.id]) { obj[d.id] = d; } }); data.links.forEach(d => { obj[d.source].childs.push(d.targetNode); obj[d.target].childs.push(d.sourceNode); }); return data; }
隨機定位
/** * @desc 獲取隨機數 */ function getRandom(min, max) { return Math.floor(min + Math.random() * (max - min)); } /** * @desc 打亂順序定位 * @param data 數據 * @param size 畫布大小 */ function randomPosition(data, size) { const { nodes, links } = data; nodes.forEach(d => { let x = getRandom(0, size); let y = getRandom(0, size); d.position = [x, y]; }); }
渲染視圖
/** * @desc 繪制 * @param ctx canvas上下文 * @param data 數據 * @param size 畫布大小 */ function render(ctx, data, size) { ctx.clearRect(0, 0, size, size); //清空所有的內容 const box = 20; ctx.fillStyle = "#FF0000"; data.links.forEach(d => { let { sourceNode, targetNode } = d; let [x1, y1] = sourceNode.position; let [x2, y2] = targetNode.position; ctx.beginPath(); //新建一條path ctx.moveTo(x1, y1); //把畫筆移動到指定的坐標 ctx.lineTo(x2, y2); //繪制一條從當前位置到指定坐標(200, 50)的直線. ctx.closePath(); ctx.stroke(); //繪制路徑。 }); data.nodes.forEach(d => { let [x, y] = d.position; ctx.fillText(d.id, x, y + box); ctx.fillRect(x - box / 2, y - box / 2, box, box); }); }
模擬作用力計算位置
/** * @desc 力算法 */ function force(data, ctx, size) { const { nodes, links } = data; // 需要參數 const maxInterval = 300; // 平衡位置間距 const maxOffset = 10; // 最大變化位移 const minOffset = 0; // 最小變化位移 const count = 100; // force次數 const attenuation = 40; // 力衰減 const doforce = () => { // 計算開始 nodes.forEach(d => { let [x1, y1] = d.position; nodes.forEach(e => { if (d.id === e.id) { return; } let [x2, y2] = e.position; // 計算兩點距離 let interval = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); // console.log("interval", d.id + "-" + e.id, interval); // 力衰減變量 let forceOffset = 0; let x3, y3; // 如果大于平橫間距,靠攏,如果小于平衡間距,排斥。這里計算第三點的坐標用到了相似三角形原理 if (interval > maxInterval) { forceOffset = (interval - maxInterval) / attenuation; // 力衰減 forceOffset = forceOffset > maxOffset ? maxOffset : forceOffset; forceOffset = forceOffset < minOffset ? minOffset : forceOffset; forceOffset += e.childs.length / attenuation; // console.log("如果大于平橫間距,靠攏", interval, d.id + "-" + e.id, ~~forceOffset); let k = forceOffset / interval; x3 = k * (x1 - x2) + x2; y3 = k * (y1 - y2) + y2; } else if (interval < maxInterval && interval > 0) { // 如果小于平橫間距,分開 forceOffset = (maxInterval - interval) / attenuation; // 力衰減 forceOffset = forceOffset > maxOffset ? maxOffset : forceOffset; forceOffset = forceOffset < minOffset ? minOffset : forceOffset; forceOffset += e.childs.length / attenuation; // console.log("如果小于平橫間距,分開", interval, d.id + "-" + e.id, ~~forceOffset); let k = forceOffset / (interval + forceOffset); x3 = (k * x1 - x2) / (k - 1); y3 = (k * y1 - y2) / (k - 1); } else { x3 = x2; y3 = y2; } // 邊界設置 x3 > size ? x3 -= 10 : null; x3 < 0 ? x3 += 10 : null; y3 > size ? y3 -= 10 : null; y3 < 0 ? y3 += 10 : null; e.position = [x3, y3]; }); }) } let countForce = 0; const forceRun = () => { setTimeout(() => { countForce++; if (countForce > count) { return; } doforce(); render(ctx, data, size); forceRun(); }, 1000 / 30) // requestAnimationFrame(forceRun); } forceRun(); }
main 函數
/* */ const size = 800; // 1.獲取數據 const data = getData(30, 0); // 2.隨機定位 randomPosition(data, size); // 3.渲染 let cav = document.getElementById("forceMap"); let ctx = cav.getContext("2d"); render(ctx, data, size); // 4.執行力算法 force(data, ctx, size);
最終生成的效果:
知識延伸這里,我們設置了最大的位移maxOffset,以及最小的位移minOffset。如果沒有達到平衡點(兩點之間距離為maxInterval)的時候,會互相靠近或者遠離,距離變化我們來的比較暴力,當然,實際上我們應該加上電子作用力(庫倫公式)和彈簧力(胡克定律),讓力導向的效果更接近自然界的作用結果。
知識延伸一下:這里我們是對nodes兩兩比較。如果我們只對兩個鏈接點進行兩兩比較,又會是這樣的結果呢,改動如下?
得到圖形:
這個代碼只是為了讓大家入門學習使用,真正的力導向算法比這個復雜的多,還可以做很多優化,比如最新版本的d3js里面的力導向算法就用四叉樹算法對其進行了優化,拋磚引玉到此為止,歡迎大家指正!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97748.html
摘要:我們在上文源碼解析發現版的節點碰撞采用四叉樹進行了優化。那么版本的力導圖具體和版的有何不同點呢,四叉樹又如何優化碰撞校驗的呢原文鏈接被重命名為。性能的提高歸功于的新的四叉樹。 我們在上文源碼解析發現v4版的節點碰撞采用四叉樹進行了優化。那么V4版本的力導圖具體和v3版的有何不同點呢,四叉樹又如何優化碰撞校驗的呢? v3-force VS v4-force https://github...
摘要:哎,其實完全可以不用力導向圖布局來處理拓撲圖的,力導向圖來處理也并不合適。初始化數據數據轉換處理可以通過力導向圖或者自己處理就行得到數據主要是鏈路可繪制坐標一開始以為,力導向圖鏈路得到的鏈路數據,會隨著節點數據位置變化而更新。 http://codepen.io/jingxiao/pe... https://bl.ocks.org/mbostock/...哎,其實完全可以不用力導向圖布...
摘要:繪制力導向圖新建畫布創建,的繪制中為了避免混亂及后續事件的添加,建議使用標簽將畫布分組。用拷貝數組,避免影響全局數據。將數據整理為樹狀結構使用樹狀布局計算位置重啟布局以改變位置在運動前強制修改節點坐標為樹狀結構 D3力導向圖及樹狀布局變換 d3的力導向圖是表現關系型數據比較方便且直觀的方法,但是會遇到節點比較多且層級關系混亂的情況,這時樹狀布局就比較方便了,如何不破壞原來結構以最小的代...
閱讀 3030·2021-11-24 09:39
閱讀 2266·2021-10-08 10:05
閱讀 2753·2021-09-24 13:52
閱讀 1572·2021-09-22 15:07
閱讀 593·2019-08-30 15:55
閱讀 1812·2019-08-30 15:53
閱讀 691·2019-08-30 15:44
閱讀 3121·2019-08-30 11:20