不久前,資料科學的話題火熱,為了讓資料有效地傳達與溝通訊息,各種資料視覺化的工具,也如雨後春筍般出現。

事實上,資料視覺化有多種角度可以切入討論,對對開發者來說,在技術面上,也並不僅止於瞭解視覺化程式庫的API。

似曾相識的D3.js

這一陣子老沉浸在3D程式建模的領域,除了享受其中樂趣,另一個好處是,就算看著技術圈子裏的熱門話題,心裡仍是老神在在,看著它們換過一個又一個,只是無論是在Maker圈或者是程式圈子裡,使用程式設計來建模,都算是非主流的課題,儘管在國外論譠裡會有與同好交流的機會,然而,在國內少了些人共同討論,有時難免覺得寂寞一些。

於是最近想到了,有沒有辦法將3D程式建模的經驗,轉化到資料視覺化上呢?一方面可以浮出水面來接觸一下熱門話題的空氣,二方面這仍是我熱愛的圖形創作領域,似乎是個一舉兩得的方式,3D程式建模本身其實就可以做資料視覺化,若能將資料視覺化的結果列印出來,感覺是也滿酷的。

不過,能把視覺化結果放到網頁?有的,OpenJSCAD(https://openjscad.org/)是OpenSCAD的JavaScript移植版本,若想用JavaScript來程式建模,而且實際列印出來,可以玩玩看。

除了OpenJSCAD之外,在3D世界想要做資料視覺化,three.js(https://threejs.org/)是一個選擇,雖然也是有興趣,然而在經過一番搜尋之後,在資料視覺化這話題下,D3.js是更多人在討論的,因為我的目的是接觸主流的空氣,就決定來玩玩D3.js。

D3.js相關資料非常多,在閱覽相關文件的過程中,我覺得相對於3D來說,D3算是簡單得多,只不過程式庫的內容非常地龐大,許多文件最後都只是在解釋API介面、可以繪製的圖案等枝微末節,看久還滿令人生厭的。

不過,某種似曾相識的感覺越來越強烈,是因為操作模式類似於jQuery?不對,玩得越久,越覺得它跟jQuery沒有關係,而熟悉感似乎是來自那種,先思考需要的資料結果,以及專注在轉換的模式!

D3.js的Enter、Exit、Update

幾乎所有D3.js的文件,開始示範程式寫作時,都是固定模式。舉例來說,如果頁面中目前完全沒有<div>元素,而有個清單data為[38,69,72,42,58,87],底下的程式碼是製作簡單長條圖的方式:

d3.select('body').selectAll('div').data(data)
.enter().append('div')
.text(datum => datum)
.style('width', datum => datum + 'px');

select('body').selectAll('div')看來像是jQuery的選擇器模式,接著,使用data(data)似乎是綁定資料,字面上看來,enter()似乎是開始輸入資料,然後加入一個<div>,文字設為datum,寬度設為datum + 'px'。如果開發者覺得這是jQuery的模式,那麼,就該發出疑問:「enter()感覺很多餘?」而且拿掉enter()一拿掉,就什麼事也不會發生!

實際上,enter()並不是輸入資料的概念,而是表示選取的元素與資料結合(join)的結果之一:「沒有元素對應的資料」。如果頁面目前完全沒有<div>元素,那麼,enter()後「沒有元素對應的資料」,就會是data清單中的全部內容,因此會加入六個<div>;如果頁面中已有兩個<div>元素,enter()後「沒有元素對應的資料」只剩下[72,42,58,87],也就只會加入四個<div>。

另一個常見的exit(),是與enter()相對立的操作,表示選取元素與資料結合的結果為「沒有資料對應的元素」,常見的是在exit()之後接上remove(),表示沒有資料對應的元素就移除吧!那麼,如果頁面中已有八個<div>,d3.select('body').selectAll('div').data(data)之後,想將「沒有資料對應的元素」文字設為0該怎麼做?接上exit().text(d => 0)就是了。

對於D3.js,在資料結合之後,除了Enter與Exit結果之外,實際上還有個Update結果,也就是d3.select('body').selectAll('div').data(…)的結果,這時,會是「有元素對應的資料」,因此,若頁面中已有六個<div>,而如果想要用[30,60,70]來更新前三個<div>,只要d3.select('body').selectAll('div').data([30,60,70]).text(d => d)就可以了。

告訴D3.js想要什麼

有不少的文件中都談到,D3.js使用上很像是更好用的jQuery,因為它將許多頁面元素的操作又做了封裝,在底層D3.js確實有這方面的動作。

然而,若使用jQuery的心智模型來撰寫D3.js,那就是個被誤導的結果了。因為,D3.js對頁面元素操作做了封裝,目的並不是模仿jQuery,而是想要開發者告訴它「想要什麼(What)」,而不是「怎麼做(How)」。

就d3.select('body').selectAll('div').data(…)這個操作來說,其實就是告訴D3.js,想要「有元素對應的資料」,如果想要進一步取得「沒有元素對應的資料」或者是「沒有資料對應的元素」,才需要分別接上enter()或者是exit(),也就是一開始,開發者就得告訴D3.js,選取的元素與資料進行結合之後,想要的結果是什麼(需要元素還是資料呢?),而在〈Thinking with Joins〉(https://goo.gl/ATtKFG)中,有兩個圓構成的圖,就非常清楚地視覺化了D3.js的三個結合結果。

一旦知道想要的結合結果之後,接下來繼續要做的,也還是告知D3.js,「資料轉換後的結果是什麼」。也就是開發者得先想清楚,在原始資料與最後底層的圖形繪製之間,到底需要哪些與多少轉換過程,無論這個過程是從說出資料的故事這方面來考量,或者是從轉換為底層技術這方面來考量。

就這邊的長條圖範例來說,沒什麼說故事的考量,由於D3.js是基於HTML元素與SVG來繪製圖形,所以,必須考量到,最後想要的是HTML?或者SVG元素是什麼?它們需要的屬性又是什麼,中間轉換過程產生的資料又是什麼?

使用D3.js的心智模型就是,考量的除了資料、資料,還是資料。有哪些原始資料?在考量這方面時,別忘了,頁面中已經用來呈現資料的元素,也算是原始資料的一部份!接著,需要哪些頁面元素及屬性來呈現圖形,元素與屬性也是資料,從原始資料到最終資料之間,又需要轉換出哪些資料?

知道D3的全名是什麼嗎?官方網站的首頁就寫明了Data-Driven Documents,使用資料驅動文件,因而若不能掌握資料、搞懂需要什麼資料,那麼,很容易就會迷失在D3.js龐雜的API之中,未先思考、研究資料之前就著手編寫程式,就容易迷失方向。

使用資料來驅動

資料不是就在我手上嗎?怎麼會還需要搞懂需要什麼資料呢?

在從事3D程式建模時,我曾有多次建模失敗經驗,這往往是因為沒能思考清楚目標模型需要的資料是什麼,然而,一旦想透了這裡需要的資料,中間需要的資料也逐一找出,目標模型就自然而然地呈現,彷彿一開始的困難與失敗都像是假的。

這就是我後來覺得D3.js的心智模型,會有種熟悉感的原因。對3D程式建模來說,乍看似乎不乏是交集、減集、聯集,只不過當這些動作出現重複時,可以用程式來解決,實際上,不只有也不該只有這樣,唯獨在能清楚知道目標模型,需要哪些座標、網格(Mesh)構造等資料的情況下,才能逐步知道,從原始資料到最終模型之間,需要什麼轉換。

轉換的結果也都會是資料,而資料又驅動另一個轉換,直到最後交給底層呈現圖形的那一刻為止,對3D程式建模是如此,對D3或者其他資料視覺化工具來說,我想也是如此。

專欄作者

熱門新聞

Advertisement