首先先推薦一下某呆翻譯的d3-force的中文文檔:https://github.com/xswei/D3-V... 。
在咱們解讀源碼前還請讀者先熟悉一下force相關的API,以及es6語法 .前端
若有分析不當之處還請留言指出,謝謝~node
那咱們進入正題吧git
咱們來看一下index.js 這個文件你們能夠理解爲force的對外統一出口。固然你也能夠自定義使用這些模塊。es6
// index.js export {default as forceCenter} from "./src/center"; // 設置力導圖點陣中心 export {default as forceCollide} from "./src/collide"; // 碰撞 export {default as forceLink} from "./src/link"; export {default as forceManyBody} from "./src/manyBody"; export {default as forceSimulation} from "./src/simulation"; export {default as forceX} from "./src/x"; export {default as forceY} from "./src/y";
其餘引用模塊github
//collide.js import constant from "./constant"; // 構造常量函數 import jiggle from "./jiggle"; // 微小晃動隨機數 import {quadtree} from "d3-quadtree"; // 四叉樹
此處代碼使用的是單例對象模式,讀者要注意,切勿與類對象理解混了。性能優化
export default function(x, y) { var nodes; // 使用閉包構建私有變量,存儲nodes。 if (x == null) x = 0; // 力導圖中心位置 x 默認值爲0 if (y == null) y = 0; // 力導圖中心位置 y 默認值爲0 // force 單例對象 function force() { var i, n = nodes.length, node, // 臨時變量用於循環 sx = 0, // 臨時變量用於計算 sy = 0; // 臨時變量用於計算 for (i = 0; i < n; ++i) { // sx = sum(node.x); 節點x之和 // sy = sum(node.y); 節點y之和 node = nodes[i], sx += node.x, sy += node.y; } for (sx = sx / n - x, sy = sy / n - y, i = 0; i < n; ++i) { // sx / n 是點陣的中心x座標;sy / n 是點陣的中心y座標。 // node.x = node.x + (x - (sx / n)); 該計算與此表達式等價,這樣讀者應該更好理解; // 座標加減即平移座標,即將整個點陣中心平移到座標(x,y) node = nodes[i], node.x -= sx, node.y -= sy; } } // 初始化,爲nodes私有變量賦值 force.initialize = function(_) { nodes = _; }; // 若是傳入參數x則設置x,不然返回當前力導圖中心位置 x force.x = function(_) { return arguments.length ? (x = +_, force) : x; }; // 若是傳入參數y則設置y,不然返回當前力導圖中心位置 y force.y = function(_) { return arguments.length ? (y = +_, force) : y; }; return force; // 返回 force對象 }
// 構造一個返回參數值的常量函數 // let a = constant(123); a() 輸出: 123 export default function(x) { return function() { return x; }; }
// jiggle.js // 微小晃動隨機數 export default function() { return (Math.random() - 0.5) * 1e-6; // 1e-6 ==> 1*10的-6次方 }
import constant from "./constant"; // 構造常量函數 import jiggle from "./jiggle"; // 微小晃動隨機數 import {quadtree} from "d3-quadtree"; // 四叉樹 // vx vy 是指當前節點的運動速度 function x(d) { return d.x + d.vx; // 運動一步 x + vx } function y(d) { return d.y + d.vy; // 運動一步 y + vy } export default function(radius) { var nodes, radii, strength = 1, // 力度 iterations = 1; // radius 設置默認值,值類型爲常量函數; if (typeof radius !== "function") radius = constant(radius == null ? 1 : +radius); // 單例對象模式 function force() { var i, n = nodes.length, tree, node, xi, yi, ri, // 半徑 ri2; // 半徑平方 // -------------- 四叉樹相關,**後文有詳細分析**---------- for (var k = 0; k < iterations; ++k) { // 以x,y訪問器構建一個四叉樹,即節點運動到下一步位置爲座標(就像咱們走夜路,探出一步試試看) // visitAfter是後序遍歷樹的節點,執行prepare爲每一個節點求半徑r,參數爲各個節點, // 返回樹的跟節點root。 tree = quadtree(nodes, x, y).visitAfter(prepare); // for循環普通遍歷節點 for (i = 0; i < n; ++i) { node = nodes[i]; ri = radii[node.index], ri2 = ri * ri; // r平方(勾股定理用) xi = node.x + node.vx;// 運動一步 x + vx yi = node.y + node.vy;// 運動一步 y + vy // 前序遍歷全部節點,apply返回true則不訪問其子節點 tree.visit(apply); } } function apply(quad, x0, y0, x1, y1) { var data = quad.data, rj = quad.r, r = ri + rj;// 兩個點與其做用域構成兩個圓,請參考以前的文章,圓與圓的碰撞測驗。 if (data) { // 存在data即葉子節點,每一個葉子節點爲一個座標點 if (data.index > node.index) { // 由於這是二重循環,全部index小於自身的點座標已經與自身判斷過了,此處是爲了不重複測驗 // 設第一重循環Node[i]爲節點A(xi,yi) 第二重循環爲節點B(data.x,data.y)下一步運動(+=vx,+=vy) var x = xi - data.x - data.vx, // Ax - Bx y = yi - data.y - data.vy, // Ay - By l = x * x + y * y; // 勾股定理 d^2 = x^2 +y^2 if (l < r * r) { // 判斷是否碰撞,若是碰撞執行如下,l:實際距離平方,r:半徑之和 if (x === 0) x = jiggle(), l += x * x; // 避免x值爲0 if (y === 0) y = jiggle(), l += y * y; // 避免y值爲0 // strength:碰撞力的強度,能夠理解爲兩點之間的斥力系數 // 見後文碰撞測驗的圖 // l = 重疊長度/實際距離 * 碰撞力度 // 重疊約多,斥力越大。斥力影響點的運動速度 l = (r - (l = Math.sqrt(l))) / l * strength; // 根據求出的斥力計算AB點新的運動速度與方向 // A點x方向的運動速度 // A速度 += B速度 -= 使得AB兩點往相反方向運動。注意,這裏的x是B到A的距離,全部是A+= ,B-= // 但斥力的緣由會使得節點的vx ,vy 趨近於0. // node.vx = B-A點x方向距離 *= 斥力 * B半徑平方(rj = B半徑平方)/( A半徑平方+B半徑平方);r = B半徑平方/( A半徑平方+B半徑平方) node.vx += (x *= l) * (r = (rj *= rj) / (ri2 + rj)); // 同x方向 node.vy += (y *= l) * r; data.vx -= x * (r = 1 - r); data.vy -= y * r; } } return; } // 若是是父節點,這裏須要讀者理解四叉樹【後面一篇文章會講解】 // 節點座標爲中心的正方形,若是沒有覆蓋到該父節點的正方形區域,這改點與此父節點的任何子節點都不會發生碰撞,則無需遍歷其子節點校驗。 // 返回true 不遍歷子節點 // 這也是v4 相比v3對性能優化最重要的一個步驟,成倍的減小計算量 return x0 > xi + r || x1 < xi - r || y0 > yi + r || y1 < yi - r; } } // 遍歷樹節點過濾器,返回true節點不可見 function prepare(quad) { // quad.data是葉子節點纔有的,因此這裏是判斷是不是葉子節點 if (quad.data) return quad.r = radii[quad.data.index]; for (var i = quad.r = 0; i < 4; ++i) { // 由於是後序遍歷,因此節點的葉子節點必定在以前已經遍歷過。 // 取葉子節點四個象限最大的r if (quad[i] && quad[i].r > quad.r) { quad.r = quad[i].r; } } } //--------------------------------------------------------------------------------------- function initialize() { if (!nodes) return; // 判斷是否有節點 var i, n = nodes.length, node; radii = new Array(n); // 按照node.index索引排序nodes 並又 radius【後文解析】 計算出半徑 後 存儲在 radii for (i = 0; i < n; ++i) node = nodes[i], radii[node.index] = +radius(node, i, nodes); } force.initialize = function(_) { nodes = _; // 賦值節點 initialize(); // 初始化 }; force.iterations = function(_) { // get or set iterations (迭代次數) return arguments.length ? (iterations = +_, force) : iterations; }; force.strength = function(_) { // get or set strength(力度) return arguments.length ? (strength = +_, force) : strength; }; force.radius = function(_) { // 前端加+號 將字符串轉爲number +"123" === 123 // 有參數: // 執行1:(radius = typeof _ === "function" ? _ : constant(+_) //radius 值是一個返回自身的函數 // 執行2:initialize() // 執行3:return force // 無參數: // 執行:return radius return arguments.length ? (radius = typeof _ === "function" ? _ : constant(+_), initialize(), force) : radius; }; return force; }
碰撞測驗閉包