從製作 visfest 2019 badge 認識 ObservableHQ

『When you get sad you run ,straight ahead and you keep running forward no matter what there are people in your life that who are going to try to hold you back , slow you down , but you don’t let them. Don’t you ever stop running, don’t ever look behind you there is nothing for you behind you. All that exists is whats ahead.』

前言

軟體工程師應該很少沒參加過 Conference 吧,不管是社群或是學術性的,只要是與會者都會拿到一面主辦方製作的名牌,上面除了印著你的大名外,大多就剩下 Conference 名稱與 Logo 了,然而,由灣區的資料視覺化社群所舉辦的年度聚會 - visfest unconf 很是特別,他們提供與會者一個製作自己 badge 的機會,讓大家自行發揮創意,特別之餘也很符合整個會議的調性。今年八月是他們舉辦的第五屆 visfest unconf,這次他們在 ObservableHQ 這個平台上釋出了一個 Template,讓大家更方便的製作名牌,效果如同下方(因為用到 ECMAScript modules,建議使用桌面版 Chrome),若無法觀看,可以前往此 link,或是拉到最下面看 gif:

除了 Observable 與 visfest 的 Logo 外,你可以繪製任何你想呈現的東西,改變背景顏色等等,而主辦方會用 gitpop 這項服務,將你的動畫製作成 10 frames 的 gif,因此在製作時,可以根據 template 提供的繪圖函式傳入的 frameNumbers 來控制動畫的呈現。

我自己是覺得這樣的想法很酷,所以即便無緣參加 visfest unconf,也製作了一個自己的 Badge 玩玩,順便來試用已經想玩很久的平台 ObservableHQ,這篇文章就記錄一下使用的過程,並說明其特性,希望能引起大家興趣!

ObservableHQ 起源

D3.js 的作者 @mbostock,在 2017 年的時候發了一篇名為 A Better Way to Code 的文章,介紹了他當時正在製作的專案 - d3.express,也就是現在的 ObservableHQ

Mike Bostock 在過去十年左右的時間都致力於資料視覺化,為了能夠更方便的將資料以各種方式呈現,D3.js 因應而生,然而,致力於一個工具太久,反而忘了當初為何要製作工具,目的變成了工具本身,而不是透過該工具所能傳達的價值。

The purpose of visualization is insight, not pictures - Ben Shneiderman

資料視覺化的重點應在於將資料的 insight 更加輕易的傳遞給世界,儘管現在已經有很多圖形化介面的拖拉工具輔助你將資料視覺化,但若是想要最大幅度的自由創作並表達 insight,撰寫程式碼是無可避免的。

然而寫程式一直都不是一件簡單的事,為了降低寫程式的難度,他創造了另一個工具:ObservableHQ。你可以把它想像成 JavaScript 版本的 jupyter notebook,但使用上某些語法跟純 JavaScript 有些差異,因此剛開始可能會有些不太習慣的地方,等到上手後,你就會發現它的好用之處,尤其適合應用在製作資料視覺化專案上。

ObservableHQ 基礎操作

ObservableHQ 其實不能算是 JavaScript,因為有許多操作行為與 vanilla JavaScript 不相同,作者也有寫了一篇說明

基本上,你可以把 ObservableHQ 想像成一個試算表,由一格一格的 cell 所組成,而每一格 cell 都是一個 JavaScript snippet,可以是一則運算式、一段簡短的函式,當然也可以將值指定給一變數名稱。

ObservableHQ statements

在上面的圖片中,比較不同的地方在於運算函式的寫法,除了一般的 Function Declarations 與 Function Expressions 外,你也可以用一個大括號 {} 包裹著一段程式碼,最後 return 出去的值,就會被印出來。

而就像試算表的公式一樣,表格內的值是互相 reference 的,只要你在某處修改了某個 cell 的值,所有引用到該 cell 的程式都會相應改變(re-run),跟一般 vanilla JavaScript 從上到下執行程式碼的邏輯有所不同:

demo

從這點來看,你也可以把所有在 ObservableHQ 上的變數 assignments 都想成是 hoisted declarations,因為宣告順序不影響 reference。

此外,支援使用 htmlmarkdown 的語法,利用 Template literals 來完成:

demo-html-md

還有一個特別的 built-in 運算子 - viewof

demo-viewof

如上面的 gif 所示,viewof 可以將使用者針對一個 html input element 操作所產生的值,exposes 出來,或是 assign 給一個變數。可以想見,這用在互動式視覺化專案中會是很棒的功能。

這幾個大概是最基本的操作,還有其他諸如:如何匯入第三方套件、使用非同步 API 呼叫等等,我們就直接透過實際演練來學習吧!

ObservableHQ 實際操作

接下來用我這次製作的 visfest badge 來做範例,進一步帶大家認識這個工具!

這次的 badge,主要概念是在 2D 地球圖上,顯示各城市的人口數量級別,用地球的轉動與 marker 的 radius 大小變化來呈現動畫。

要完成這個目標,起手第一步就是得先把 2D 地球畫出來。而 Mike Bostock 有提供一份 topojson 格式的世界地圖資料:https://unpkg.com/[email protected]/world/110m.json

因此,我們需要載入 topojson 套件,並 fetch 世界圖資,而在 ObservableHQ 上我們可以這樣做:

Import libs

這邊我們用 require 來載入 topojson 套件,但其實它背後並非 CommonJS,而是用 Asynchronous Module Definition (AMD) 實作。另外它也支援 ES modules 與 imports,所以可以依照你所使用的套件支援度來選擇要用哪種方式載入模組。

至於取得世界圖資,我們需要兩個步驟:

  1. 取得圖資 JSON 檔案:let world = await (await fetch("https://unpkg.com/[email protected]/world/110m.json")).json();
  2. 取得拓樸後的資料:topojson.feature(world, world.objects.countries);

在 ObservableHQ 上,我們可以利用先前說到的大括號 {} 來執行多行程式碼,並把最後 return 的值賦予給某個變數:

Get world map data

接著還需要有各城市的人口資料,取得方式與上面相同:

Get cities population

資料到手後,剩下的就就跟一般撰寫 D3.js 的專案ㄧ樣。

在 visfest 提供的模板中,提供了許多以定義好的變數宣告,像是姓名、字體亮度、大小等等,而我們只需要在 badgeCode 這個函數中實作我們的視覺化作品即可。

1
2
3
4
5
6
7
8
9
// Your D3 code goes here.
// The function badgeCode gets called below to generate the previews.
function badgeCode(g, c, frameNumber) {
// g: a <g>, i.e. d3.select(... the node)
// context: a canvas context, useful for doing canvas drawing
// frameNumber: a number that represents the frame (1, 2, … 10) for psuedo-animation.
// width & height are available via the environment, as is d3
// Note: the <g> is drawn on top of the <canvas>
}

badgeCode 接收三個參數,一個是在 badge 上留給你繪圖的空間 <g> 元素,以及 canvas contextframeNumber,該模板會呼叫此函式十次,依序傳入遞增的 frameNumber,讓你來掌控動畫過程。

到這邊可能你會好奇,在 ObservableHQ 中的 模板 到底是怎麼運作的?為什麼能定義出一個函式,讓我去填寫內容,又幫我執行呢?

答案要追究到 ObservableHQ 的一個特殊 import-with 方法:

import-with

b93171820ba3f268 是 ObservableHQ 上的另一個 notebook,也就是實際的 template 程式碼所在位置,我們可以從該 notebook 中 import 進 {preview, animation, download, width, height, d3} 這幾個函式來呼叫使用,而這幾個函式中所用到的變數,我們能使用 with 來取出,並且賦予其新的值!

這就是為什麼我們可以修改 firstNamelastName 以及自行填入 badgeCode 函數的原因了。

badgeCode 內的程式碼基本上就是普通的 D3.js 程式,這邊就不再附上程式碼,有興趣想知道怎麼實作在地球呈現人口分布的,可以到我的 notebook 去看,其中我也有用到前面提及的 viewof 運算子,讓我能手動調整地球的 scale 大小。

都完成後就會看到由模板提供的 preview 函式所繪製出的十張圖:

Preview ten pics

以及用 animation 函式(一樣模板提供)繪製的動畫:

Animation gif

分享你的 ObservableHQ 作品

完成作品後,最重要的就是分享。

這篇文章的最上方,我放入的成品並不是 gif,而是貨真價實從 ObservableHQ 所匯入的,利用 ECMAScript modules,載入想要嵌入的 notebook,接著再載入 ObservableHQ 提供的 {Inspector, Runtime},當 notebook 載入後,取得其中 export 的變數,透過 Inspector 將其繪製到指定的 DOM id 上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="animation"></div>
<script type="module">
import notebook from "https://cors-anywhere.herokuapp.com/https://api.observablehq.com/@arvinh/visfest-unconf-badge-builder-template.js";
const renders = {
"result": "#animation",
};
import {Inspector, Runtime} from "https://unpkg.com/@observablehq/[email protected]?module";
for (let i in renders) {
renders[i] = document.querySelector(renders[i]);
}
Runtime.load(notebook, (variable) => {
if (renders[variable.name]) {
return new Inspector(renders[variable.name]);
}
});
</script>

透過這種方式,除了能夠在 ObservableHQ 上載入引用他人的 notebook 外,也能在一般網站上嵌入任何作品,非常方便!

結論

這篇文章只是非常簡略的說明了 ObservableHQ 的起源、用途與使用方式,希望能引起大家的興趣,如果有想要使用這套工具玩玩,或是製作視覺化專案的話,官方網站其實有出了一系列的教學與說明文件,直接就是用 ObservableHQ 的 notebook 撰寫的,互動式的閱讀體驗非常好,可以非常清楚的知道各個環節該怎麼使用,以及其背後的設計原理。

資料來源

  1. Observablehq five minute intro
  2. Observable’s not JavaScript
  3. Observablehq introduction