農林漁牧網

您現在的位置是:首頁 > 畜牧業

手寫動態 3D 蛛網圖 | THREE.js

2022-02-14由 澎湃線上 發表于 畜牧業

蛛網圖怎麼畫

原創 服老思 P話 收錄於話題#3D1#視覺化1#演算法2#資料2

雷達圖容易誤用,所以我一般不用。從資料視覺化的最佳實踐來說,不推薦 3D,因為透視關係導致無法準確比較。同時,不推薦動態圖,人老了後看起來眼花,當然某些場景下引入時間維度後,可以增強資訊密度,如動態柱狀圖和動態折線圖。

我們做人有原則,要麼嚴格遵守,要麼三條一起破壞。

所以,今天做一個 3D 的、動態的雷達圖。最後選擇用 THREE。js 實現主體,用 DOM 實現控制元件和互動。

Hacking THREE

之前 解析《自然》 雜誌 150 年論文視覺化作品 中,提到過這個庫。THREE。js 基於 WebGL,前身是廣泛用於 3D 遊戲的 OpenGL。隨著移動硬體效能的提升,OpenGL 這套標準介面,在 web 環境中,也普及開來。而 THREE 對 WebGL 進行了更友好的封裝,使得沒有計算機圖形學的開發者,也能夠很快地搭建自己的 3D 場景。

手寫動態 3D 蛛網圖 | THREE.js

推薦網站:https://threejsfundamentals。org/

如果追求速度的話,看過 Fundamentals、Responsive Design、Primitives 這三章,就可以開始 hack 自己的專案了。如果趕時間的話, Responsive Design 一節可以後面來看。不過推薦放在前面,這樣可以讓自己的設計從一開始避免鋸齒、拉伸等問題,之後可以專注在圖形上。閱讀加練習大概 2-3 小時。

手寫動態 3D 蛛網圖 | THREE.js

https://threejsfundamentals。org/threejs/lessons/threejs-fundamentals。html

基本概念:

Renderer:渲染引擎,它的輸入是 scene 和 camera

Object:一個基本單元

Scene/ scene graph:類似資料夾,scene 和包含 object,scene 可以巢狀

Geometry:幾何形狀,具體包括點(vector3)、面(face3)

Material:材質,應用在面上

Texture:紋理,應用在材質上

Mesh:Geometry 和 Material 的組合;Geometry 決定了畫什麼、Material 決定了怎麼畫

Light:光源

Camera:鏡頭

THREE 的世界中,hello world 就是一個 “Hello Cube”,即畫一個方塊。跟教程的第一個 milestone 大概是上面這樣,有幾個 cube,不停翻轉。

手寫動態 3D 蛛網圖 | THREE.js

https://threejsfundamentals。org/threejs/lessons/threejs-fundamentals。html

如果是初學,建議在這裡多花一點時間,調整各項引數,觀察結果,以掌握基本概念。否則,很容易花了大把功夫後,怎麼也調不出自己要的圖形。

我遇到的幾個常見問題有:

相機方向問題,物件不在視野內

視域的長度不夠(far)

沒有加光源

光源沒有對準物體

座標系錯誤。注意右手法則。x 向右(拇指)、y 向上(食指)、z 從螢幕指向自己(中指)。

角度錯誤。注意 THREE 裡面的旋轉(rotation)用弧度(radian),但相機的設定中,fov 用角度(degree)。

面的方向(法向量)

有了 Hello Cube 後,可以進一步嘗試光照、相機、材質等不同設定。

接下來,就可以探索 THREE 裡面現成的形狀,把它們拼接起來就可以組成複雜的圖形。原網頁可以掃碼檢視。

https://threejsfundamentals。org/threejs/lessons/threejs-primitives。html

可以看到,有許多現成的圖形。

比如,下面演示瞭如何畫一個 3D 的心形。這段程式碼的功能其實更廣,它示範瞭如何透過一系列 2D 的點,透過貝塞爾曲線連線起來,再生成一個 3D 空間中的面。

手寫動態 3D 蛛網圖 | THREE.js

https://threejsfundamentals。org/threejs/lessons/threejs-primitives。html

又比如,下面演示瞭如何透過向量字型,生成三體空間中的形狀。

手寫動態 3D 蛛網圖 | THREE.js

https://threejsfundamentals。org/threejs/lessons/threejs-primitives。html

我們的目標是蛛網圖,每一個數據點是一個多面柱,它們透過顏色和高度來彼此區分,多面柱的每個稜角,就是一個維度。所以,基本的單元是多面柱。我們在 THREE 的 primitive 裡面尋找,最接近的是下面這個形狀。

手寫動態 3D 蛛網圖 | THREE.js

https://threejsfundamentals。org/threejs/lessons/threejs-primitives。html

可是它有一個問題:所有的稜都是一樣的長度。

我們的資料視覺化中,需要根據資料值的不同,來設定稜的長度。

於是,這裡要做一點探索。首選的方案,是非侵入式的。即如果能透過 THREE 已經有的介面,從外部改變這個多面柱的特性,達到我們的視覺化效果,則為上策。實在不行,可以選擇魔改一把 THREE,再硬嵌入到專案中。

這就回到補充基本概念的時候。

手寫動態 3D 蛛網圖 | THREE.js

https://threejsfundamentals。org/threejs/lessons/threejs-primitives。html

在 THREE 中,模型是透過 Gemoetry 設定的。Geometry 的設定有兩部分:

設定一系列的頂點。每個頂點由 (x, y, z) 這樣一個三元組宣告,代表空間中的座標。

設定一系列的三角形。每個頂點由 (id1, id2, id3) 這樣的三元組宣告,代表點集中下標。

所以,如果要程式化地改變一個形狀,只需要改變頂點的位置,不用改變三角形的設定,與該頂點相連的線和麵就會自動地改變。不過注意要把 verticesNeedUpdate 和 elementsNeedUpdate 兩個標記打上,這樣在下次渲染的時候,引擎會做相應的更新。

在 Hello Cube 的基礎上,做如下改變測試。

手寫動態 3D 蛛網圖 | THREE.js

透過改變頂點,實現形狀變化

其中一個頂點,會以 8 秒為週期,伸長後又縮短。可以看到,鄰接的面都配合變化,非常平滑。

手寫動態 3D 蛛網圖 | THREE.js

經過這個實驗,我們就有信心,一定能透過一些 primitive,以及修改頂點的方法,來實現可變尺寸的多面柱(VariableCylinder)。

不過,從外部實現的話,要修改的頂點比較多。其中涉及到極座標的公式,核心計算部分和普通多面柱(Cylinder)是重複的。另外,修改完頂點,還要重新計算 UV 和法向量,才能正確響應光照和紋理的設定。

考慮到這些,覺得先看一下原始碼比較好,也許直接改庫更方便。

原始碼:https://github。com/mrdoob/three。js/blob/master/src/geometries/CylinderGeometry。js

將這個 CylinderGeometry 複製一份,做成我們自己的 VariableCylinderGeometry。事實證明,只需要修改 7 行,就達到目的了……

手寫動態 3D 蛛網圖 | THREE.js

當然,為了修改這 7 行,需要花點時間研究 THREE 這個專案的結構。另外,由於是可變的多面柱,資料介面發生了一些變化。之前只需要輸入稜的個數,而現在要輸入的是每個稜的相對長度。

想起了福特和斯坦門茨的那個廣為流傳的段子:畫一根線,只要一美元;知道在哪裡畫這根線,值 9999 美元。

比較好的方法,可能是從模組級別整合 THREE,然後自己派生出這個可變多面柱的類。由於對前端工程不熟悉,一看到近年的各種框架和 import 就頭大。於是選擇了魔改路線,直接侵入式地做了需要的修改,然後重新 build 一個 THREE 嵌在專案裡面。

匯入魔改版的THREE,將 HelloCube 改一下,得到了多面柱:

手寫動態 3D 蛛網圖 | THREE.js

可變多面柱

接下來比較重要的部分就是蛛網了。計算核心還是極座標變換。得到關鍵點後,放入一個 Geometry,再透過 THREE。Line 這個輔助工具,轉化成可渲染的圖形。注意 THREE。Line 的地位和 THREE。Mesh 是類似的。而 Geometry 中,有點和麵,但是沒有線。

蛛網的大致效果如下。

手寫動態 3D 蛛網圖 | THREE.js

大家可能發現,前面的模型,都是用黑色作為背景。以前我也覺得奇怪,是不是設計師喜歡黑色背景,看起來更高階?

其實更直接的原因是,在 3D 的世界中,一切本來就是黑的。因為有了光、有了物體,而光照到物體上,我們才看到不同的顏色。哪怕是背景,也是如此。如果使用者看到的不是黑色,說明有東西在那,它反光。

所以,我們需要在所有的物件下面(-y 的區域),加一個白色的面,作為襯底。

把以上物件組合在一起,就得到第一個原型了。

手寫動態 3D 蛛網圖 | THREE.js

接著,在 canvas 的上面,加一個浮層,放入座標標籤和相對應的值。再給 legend 加上事件,當用戶選擇的時候,可以高亮相應的多面柱。最後,再調一下顏色。這部分和平時寫的前端沒什麼差別,就略過細節了。

手寫動態 3D 蛛網圖 | THREE.js

一個 3D 動態蛛網圖,就做成了。

手寫動態 3D 蛛網圖 | THREE.js

這個圖有什麼用呢?

它比較適合資料點在各個維度都單調遞增的場景。比如,用來描述一個同學的成績,每個軸代表一個學科,每個多面柱代表一個年份。隨著努力學習,他成績越來越好,就是這樣的感覺。如果不滿足這個單調遞增,則效果是很奇怪的,下層的柱子,可能有一部分 “陷入” 上層的柱子中。

覆盤

時間開銷:

調研:10%

入門 THREE:20%

Hacking prototype:30%

調整細節:40%

其中,調研階段考慮了 aframe 和 THREE 兩個庫。aframe 是基於 THREE 的一套 declarative 語言。透過 HTML 元素,來生成 3D 的物件。如果是簡單的圖形,用 aframe 確實很方便。經過幾年的發展,它有了一個強大的社群,其中有不少參考專案。另外,還內建一個所見即所得(WYSIWYG)的編輯器,對於調整細節非常方便。

手寫動態 3D 蛛網圖 | THREE.js

Aframe 內建編輯器 (ctrl+alt+i)

對於稍微複雜的專案,可以透過建模軟體,如 Maya 建立模型,再匯入 aframe,利用前端技術做互動。這樣開發者不需要有關於 3D 圖形的知識,只需要複用 DOM 互動的經驗即可,可以滿足快速開發的需求。

不過,當圖形是資料驅動生成的時候,發現 aframe 就有點差強人意了。程式化地修改,可以比較方便完成的是:

平移

縮放

旋轉

組合

如果要改變一個形狀本身,比如這次我們需要的可變多面體,aframe 就需要一個 custom model。而 custom model 是用 THREE 寫的…… 所以結果是,不如直接學習 THREE 好了。

這是前期調研走的一點彎路。該來的遲早會來,怎麼也擋不住。不過好在 THREE 的封裝已經極其方便,相比十年前學習OpenGL,已經快了很多。由於都是在前端,可以綜合使用 WebGL 和 DOM 兩種技術,利用浮層實現假 3D 的效果,也能省下不少時間。如果放在古時候,到這會可能才把 Hello Cube 搞定……

CREDIT

排版:Bee

資料科學 | 數字廣告 | 未來主義

原標題:《手寫動態 3D 蛛網圖 | THREE。js》

閱讀原文