D3 是目前最流行的數據可視化庫,WebGL 是目前 Web 端最快的繪製技術。因爲性能問題的侷限,將二者結合的嘗試愈來愈多(如),本文將嘗試用 D3 的力導向圖 和 Three.js 和 PixiJS 結合。全文閱讀完大概 5 分鐘,由於你重點應該看代碼。node
作數據可視化時,必然會考慮性能的問題。早前數據可視化都是用 Qt 等 GUI,後來逐漸遷移到了迅猛發展的瀏覽器上展現,Web 的性能問題成了大多數可視化的侷限,尤爲是在三維可視化,或數據量特別大的時候。如今主流的 Web 可視化技術爲三種:SVG、Canvas 和 WebGL,難易程度和性能以下圖:git
SVG 的優勢不少,編輯簡單,交互便捷,靈活性極高,業內成熟的可視化工具(如 d3)都是用的 SVG。可是每一個 SVG 都是一個 DOM 元素,隨着它的數量上來以後,交互開始慢的難以忍受。這是由於每當修改一個 DOM 對象,只要這個對象在文檔裏,接着在瀏覽器裏就會發生兩個動做,一個叫 Reflow(重排,就是從新排版),另外一個叫 Repaint(重繪,就是從新渲染頁面)。這兩個動做不必定都會發生,但若是被修改的 DOM 當前可見的話,那麼就會先重排,後重繪。繪製性能上 canvas 和 SVG(DOM 元素)應該差很少,但前者能夠省掉重排過程,所以性能更高。然而,WebGL 的性能更勝一籌,由於 WebGL 使用 GPU 加速渲染,GPU 在大規模計算方面有絕對優點(圖像處理、深度學習都在用,顯卡已經賣瘋了)。例子:用 WebGL 繪製 200000 個點的動畫(http://rickyreusser.com/smoot...github
WebGL 雖然威力無窮,可是寫起來比較痛苦,畫個三角形大體要 100 行代碼。因此不少人對 WebGL 進行了封裝。上面圖中提到的兩個 Three.js 和 PixiJS 是目前最流行的兩款 WebGL 庫,固然還有新興的 regl 在今年的 OpenVis 上大放異彩。本文嘗試用前二者和 d3-force 結合(項目代碼在此),後面若是有時間的話,我會把使用 regl 和原生 WebGL 的例子也補充進去(我知道這是個 flag)。web
首先咱們要知道什麼是力導向圖和如何使用 d3-force。d3 4.0 以後,做者將其模塊化,force 這個模塊是基於 velocity Verlet 實現了物理粒子之間的做用力的仿真,經常使用於網絡或關係結構數據。即你把網絡中的節點想象成一個個粒子,它們之間互相有做用力,因此不停的拉扯,直到趨於一個穩定狀態,具體能夠看我 demo 中可視化出來的樣子。canvas
仔細看 demo 中的源碼能夠發現,用 three.js 和用 pixi.js 實現起來很是相似,其中有關力導向圖的關鍵代碼是下面幾句:數組
const simulation = d3.forceSimulation() // 建立一個做用力的仿真,但此時還沒啓動 .force('link', d3.forceLink().id((d) => d.id)) // 爲邊之間添加 Link 型做用力 .force('charge', d3.forceManyBody()) // 指定節點間的做用力類型爲 Many-Body 型 .force('center', d3.forceCenter(width / 2, height / 2)) // Centering 做用力指定佈局圍繞的中心
d3-force 提供了五種做用力,分別是 Centering、Collision、Links、Many-Body、Positioning。此時咱們已經建立好帶有各類力的仿真器了,接下來須要啓動它:瀏覽器
simulation .nodes(data.nodes) // 根據 data.nodes 數組來計算點之間的做用力,至關於不停計算節點的 xy 座標 .on('tick', ticked) // 每次 tick 調用 ticked simulation.force('link') .links(data.links) // 根據 data.links 數據計算邊之間的做用力
至此一個力導向圖的仿真就開始了,那麼怎麼把這些節點和邊顯示出來呢?讓咱們繼續看源碼,以 three.js 爲例:網絡
const scene = new THREE.Scene() const camera = new THREE.OrthographicCamera(0, width, height, 0, 1, 1000) const renderer = new THREE.WebGLRenderer({alpha: true}) renderer.setSize(width, height) container.appendChild(renderer.domElement) // container 這裏是 document.body
在 Three.js 中展現場景須要具有三要素:場景、照相機、渲染器。照相機就至關於咱們的眼睛,它對着渲染好的場景就至關於把場景成像到了相機中,這裏的照相機咱們用的是平行投影相機,渲染器咱們使用的是 WebGL 渲染器。設置好渲染器的大小,把它添加到頁面的元素上,至關於添加了一個 <canvas>
元素。接下來,咱們生成每一個節點和邊的樣子:app
data.nodes.forEach((node) => { node.geometry = new THREE.CircleBufferGeometry(5, 32) node.material = new THREE.MeshBasicMaterial({ color: colour(node.id) }) node.circle = new THREE.Mesh(node.geometry, node.material) scene.add(node.circle) }) data.links.forEach((link) => { link.material = new THREE.LineBasicMaterial({ color: 0xAAAAAA }) link.geometry = new THREE.Geometry() link.line = new THREE.Line(link.geometry, link.material) scene.add(link.line) })
套路都同樣,都是先建一個幾何體,而後設置材質的樣式,添加到場景中就行了。接下來只要在剛纔提到的 ticked 這個回調函數中把節點和邊的座標更新一下就行了:dom
function ticked () { data.nodes.forEach((node) => { const { x, y, circle } = node circle.position.set(x, y, 0) }) data.links.forEach((link) => { const { source, target, line } = link line.geometry.verticesNeedUpdate = true line.geometry.vertices[0] = new THREE.Vector3(source.x, source.y, -1) line.geometry.vertices[1] = new THREE.Vector3(target.x, target.y, -1) }) render(scene, camera) }
是否是比想象的簡單多了?若是以上有什麼地方看不懂,說明你可能對 Three.js 不是很瞭解,不過不要緊,它的文檔寫的很好,入門很快。但願這篇文章能給你帶來一些幫助,作了點微小的貢獻,很慚愧 :)
本做品採用知識共享 署名-非商業性使用-禁止演繹 4.0 國際 許可協議進行許可。