Folio
Folio 圖版用自然歷史博物館解釋標本的方式來解釋一個概念。底座上有一個物件,旁邊有編號索引,牆面文字則告訴你如何觀看它。這裡的物件是你發明的儀器,例如長凳、天平、織機或蒸餾器,其運作部件能執行這個概念。讀者可以撥弄它,上面的每個讀數都是計算出來的。
三項承諾
- 機制,而非隱喻。 儀器必須執行這個概念,而非作為插圖站在一旁。在繪製之前,先寫下一行:「當讀者執行 X 時,部件 Y 會移動,而這個移動就是概念中的 Z。」
- 單一事實來源。 一個小型純模型計算所有東西:指針角度、液體高度、讀數、索引、表格。頁面只顯示模型回傳的結果,因此不會有兩個地方產生不一致。
- 引導式閱讀。 好的圖畫仍需要引導。每個圖版都附有索引、逐項解讀、完整運作過程,以及對簡化部分的老實說明。
開始使用
- 儲存庫附有三個參考圖版。圖版 I(
examples/attention/,注意力機制作為分析長凳)是處理由離散步驟和公式組成之概念的模型。圖版 II(examples/heart/,人類心臟作為雙重泵)是處理隨時間運行之事物的模型:逐幀整合的模擬、連續流動、相位以及每個週期後重新繪製的圖表。圖版 III(examples/grid/,微型電網)是處理電力、磁力和能量流動的模型,也是由獨立機器(而非傳送帶)構成的長凳模型:渦輪機、帶旋轉轉子的發電機、變壓器、電塔和城鎮。圖版 IV(examples/hashmap/,雜湊表作為分信辦公室)是處理演算法和資料結構的模型,概念是一系列離散操作:每個操作(插入、查詢、調整大小)擴展成一系列計時的動作(提起、攜帶、浸入比較、將所有卡片飛到新位置),一個接一個執行,讓讀者觀看演算法逐步進行的過程。先打開最接近的那個。 - 最快的方式是複製一個參考圖版:
node scripts/new-plate.mjs <name>會從圖版 I 建立examples/<name>/。然後替換其模型、場景和文字。從一個可運作的圖版開始建構,比從頭開始更快速且更安全。 - 共享的展覽樣式位於
kit/folio-page.css;圖版的page.html會在其<style>標籤內放置/*CSS*/,並僅添加其專屬規則。 node scripts/build-site.mjs會將每個圖版以及封面頁(site/index.html)建構成docs/,一個準備好讓 GitHub Pages 使用的網站。要新增圖版,請將名稱加入該檔案的PLATES陣列,並在封面頁加入卡片。- 使用
node scripts/build.mjs examples/<name> dist/<name>.html將任何圖版建構成單一獨立檔案。 - 如果只安裝了這個檔案(沒有儲存庫),附錄部分包含了繪圖函式庫;請將其儲存為
folio-kit.js。
一個圖版由五個檔案組成:
| 檔案 | 角色 |
|---|---|
folio-kit.js | 繪圖函式庫(共享,從不依圖版編輯) |
folio-live.js | 三個視圖:舞台、光源圖層、軌道、視圖切換(共享) |
model.js | 純模型;將一個物件放到 window 上 |
plate.js | 儀器:靜態幾何形狀、動態插槽、輸入 |
page.js | 索引、控制項、牆面文字、分類帳;監聽圖版 |
page.html | 底座與樣式,包含 /*KIT*/ /*MODEL*/ /*PLATE*/ /*PAGE*/ 佔位符 |
階段 A:簡報(還不畫圖)
A1. 範圍。 在開始之前,先告訴使用者他們要的是以下哪一種,以及大概需要多少時間:
| 範圍 | 內容 | 典型時間 |
|---|---|---|
| 草稿 | 儀器 + 索引 + 讀數,沒有牆面文字 | 少於一小時 |
| 圖版 | 完整的展覽頁面(「解釋 X」的預設選項) | 一個半到三小時 |
| 系列 | 在一個共享的長凳、尺度和調色板上的一系列圖版 | 先協商清單,然後一次一個圖版 |
A2. 模型。 在開始任何視覺化工作之前,先撰寫 model.js。收集真實的公式、單位和數量級。回傳純物件:輸入參數,以及頁面會顯示的每一個數量。在 Node 中執行並印出幾個案例。現在就尋找邊緣情況(空的集合、被遮罩的行、極限值)。如果有任何發明的事物(例如玩具權重或樣本資料),請在「關於本標本」的牆面文字中註明。
A2b. 隨時間運行的模型。 當概念是一個過程(心跳、佇列清空、學習曲線)時,將其撰寫為狀態加上 step(s, dt) 函數,以及一個回傳所有顯示內容的 read(s)。在繪圖之前,先在 Node 中針對已知數值調整參數,如同圖版 II 對休息中的成年人所做的那樣(約 115/80 mmHg,每次搏動 70 mL,每分鐘 5 L)。當圖畫離開螢幕時,保持模擬持續運行,只跳過繪畫動作;否則,在頁面更下方更改控制項會導致數字凍結。
A3. 儀器及其對照表。 從實驗室、作坊、天文臺或印刷廠的世界中尋找一個儀器。然後撰寫將每個術語對應到一個部件的表格:
| 概念中的術語 | 儀器部件 | 讀者能看到什麼 |
|---|---|---|
| query | 架空軌道上的承載器 | 它停在正在提問的詞上方 |
| scaled score | 每個詞一個刻度盤 | 指針位置 |
| softmax | 一個密封的儲液槽 | 這些柱子只能分配它所容納的量 |
| mask | 帶蓋的旋塞 | 後面的詞被關閉 |
| weighted sum | 有分層沉積物的托盤 | 每一層的長度就是一個權重 |
每一行都需要一個部件,每個主要部件也都需要一行。一些可靠的映射:固定的總和變成儲液槽、錢包或一袋砝碼。比較變成刻度盤、天平或卡尺。選擇變成閥門、開關或道岔桿。累積變成層、堆疊或計數器。排序變成軌道、架子或滑槽。
階段 B:長凳
B1. 清單。 在擺放之前先列出所有部件。目標是 30 到 60 個。依用途分組:術語部件(來自表格,每個項目重複)、支撐部件(底座、腳、支柱、軌道、套環、支架)、控制部件(選擇器、滾花旋鈕、槓桿)以及安裝細節(目錄牌、蝕刻標籤、刻度條、螺絲)。只有一個數量——也就是概念所圍繞的那個——會用紅色標示。
B2. 層次。 使用世界座標,x 軸沿著長凳,y 軸朝向觀看者,z 軸向上。將長凳從後到前劃分為深度層次,並賦予每個層次一種部件。圖版 I 的層次順序是:軌道、柱子、旋塞、歧管、刻度盤、托盤。在世界單位中,在層次之間留下間隙,因為一個戳進下一層次的部件會以錯誤的順序繪製。在一個層次內部,從遠到近繪製。設定相機(az 對於長長凳約為 -24,el 為 30),並使用 Folio.fit 使其吻合邊界。
B3. 靜態字串,動態插槽。 走訪每個層次一次,將 SVG 字串推入一個有序列表。在任何會移動的東西處,改為推入一個命名插槽。在組裝時,列表會變成一個 SVG,其中每個插槽對應一個空的 <g> 元素。每個動畫幀只會重新繪製插槽,使用追逐模型目標值的緩動數值。一旦所有數值都到達目標,繪製就會停止。
B4. 針。 直接在承載概念的部件上放置 6 到 10 個編號針,編號順序按照新手應該查看它們的順序。針是唯一覆蓋在圖畫上的標記。所有其他文字都被蝕刻到一個表面上:底墊正面的名稱、刻度上的數字、底座上的目錄牌。
階段 C:底座
頁面是解釋的一部分。從上到下使用以下區塊建構:
- 題頭。 左邊是系列名稱,中間是字標,右邊是圖版編號。
- 標題區塊。 一個展示用標題,將概念命名為儀器(「注意力機制,作為一個分析長凳。」),三個句子說明發生了什麼事,以及一個小型目錄清單:物體、概念、模型、目錄。
- 嵌入圖版。 一個帶有極細框線內框的紙質底座。其四個角承載圖版編號、簡短說明、互動提示和即時讀數。
- 索引。 每個針對應一個條目,提供部件名稱和一行說明它計算了什麼,數學公式用等寬字體顯示。游標懸停在條目上會高亮其針,懸停在針上則會高亮其條目。
- 在長凳旁。 普通的按鈕和滑桿,用來做儀器本身旋鈕能做的事。
- 牆面文字 I:閱讀圖版。 按照公式書寫的順序,每個術語一個欄位:羅馬數字、動詞、術語、兩句引用針號碼的句子,以及一個顯示當前數字的方框。
- 牆面文字 II:分類帳。 當前狀態的完整運作過程,每個項目一行。點擊一行會選取該項目。
- 牆面文字 III:更廣的視野。 儀器所展示的一個切片(完整的矩陣、整個序列、每個桶子),以及選擇器可選擇的變體。
- 牆面文字 IV:關於本標本。 三則註記:什麼是精確計算的,什麼是手工選擇的,以及什麼被省略了。總是包含這個區塊。
- 版權頁。 誰製作了這個圖版以及它使用了哪些字體。
控制項回應指針和鍵盤:點擊一個站點,方向鍵步進,數字鍵選擇變體,括號鍵轉動旋鈕,一個字母鍵切換開關。當沒有人互動時,圖版會緩慢地自我展示。在懸停或聚焦時暫停,在捲離時靜止,並在減少動畫設定下顯示一個靜止幀。讀數使用 aria-live。
階段 C2:三個視圖
每個圖版都以三種方式提供相同的儀器,透過底座頂角的一個小開關(SVG · WebGL · 3D)進行選擇。模型、索引和牆面文字在視圖之間不會改變;只有展示長凳的方式會改變。
SVG 是上述的蝕刻圖畫。它是預設視圖,也是唯一必須始終運作的視圖。
WebGL 在不重新繪製圖畫的情況下,將光線疊加在圖畫之上。光存在於 SVG 上方的畫布中,它透過一個從圖畫本身渲染出的遮罩來知道什麼是可見的:light() 會複製 SVG,根據角色重新為每個表面著色(地板為紅色,容器和管道為綠色,玻璃為藍色,其他所有東西為黑色),保持繪圖者的順序,並將結果上傳為一個紋理。圖畫隱藏起來的任何東西,光線都無法觸及,因此不需要單獨的遮蔽邏輯。使用類別(底座用 floor,高起的接收器用 pad,容器用 pipe 或血液類別)賦予每個表面其角色,並將顏色規則作為 maskCss 傳遞。
在著色器內部,onPlane(vb, z) 將一個像素轉回水平面上的世界點,project() 則相反,fall() 找出光線穿過一個部件後落在地板上的位置(燈具位於長凳後方,所以光斑落在部件前方,讀者可以看到的地方)。只對主題數量使用光效:
- 圖版 I: 每個柱子的流體根據其權重發光,一道波紋光柱從每個柱子穿過長凳,並且脈衝沿著歧管運行,在權重變動時速度更快。
- 圖版 II: 脈衝以真實流量(與移動 SVG 流量標記相同的累積體積)沿著每個血管行進,每個腔室發出其血液顏色的光,並且心室在擠壓時閃爍。
使用預乘顏色(addLight)累積光線,並根據主題選擇顏色:紙質表面上的淡色高光和飽和光斑(正常混合),深色背景上的較亮輝光(螢幕混合,由 --light-blend 標記設定)。僅在視圖處於活動狀態且圖版在螢幕上時才渲染,將像素比率限制在 1.5,並在減少動畫設定下繪製一個靜止幀。
3D 將長凳完全旋轉。由於整個場景是相機的函數,stage.mount(az, el) 會從新角度重新建構它:每個面根據其朝向重新著色,每個圓形部件重新產生輪廓,每個陰影線重新間隔,每個蝕刻標籤重新投影。從每個側面都必須保持繪圖者的順序,因此絕不要硬編碼「從後到前」:
- 將每個深度層次包裝為
{ y, draw },並將列表傳遞給api.bands(list);它會根據當前相機從遠到近排序。從背面看,前面的層次會先繪製。 - 在一個層次內部,或者對於由獨立機器構成的長凳,賦予每個單元一個世界位置,並使用
api.units(list)搭配{ at: [x, y, z], draw }。圖版 III 完全以這種方式建構。 - 開口容器(托盤、水槽)的側壁使用
api.backIsFar和api.leftIsFar進行排序。 - 任何安裝在底座一個面上的東西(黃銅目錄牌、前面板上的計時凸輪)僅在該面朝向讀者時才繪製:首先檢查
cam.view的正負號。faceText已經會自行剔除。
orbit({ wrap: true }) 允許方位角超過 360°,stage 會在每個角度重新調整比例(由 orbitScale 限制,因此長凳的大小不會跳動),雙擊返回預設視圖,Shift 搭配方向鍵從鍵盤旋轉它。使用兩個仰角下每 45° 的網格角度進行檢查。圖版 II 的完整重建大約需要 27 毫秒,這足夠流暢地拖動;如果一個圖版更重,請記住每個插槽的最後輸出,並只重新繪製有變化的部分,如同圖版 III 所做的。
離開 3D 視圖會將相機返回到預設視圖,因此 WebGL 光效(假設使用預設視圖)總是能對齊。
階段 C2b:移動的內部零件
一個圖版絕不應該是一個旁邊有數字的靜止圖片。找到那些真正會移動的部件,並以眼睛能跟上的速度由模型驅動它們:
- 會轉動的東西。 視鏡中的流量輪以真實流量轉動(圖版 I 的歧管),計時凸輪每個週期轉動一圈並顯示哪個相位處於活動狀態(圖版 II),發電機的轉子、水輪和飛球調速器以機器的速度轉動(圖版 III)。從模型累積角度(
rot += ω·dt),而不是從時鐘,這樣一台減速的機器就會明顯地慢下來。 - 會移動的東西。 液位、活塞、閥門瓣、指針、調速器隨著速度增加而飛出的球、斷路器刀片舉起。
- 會互動的東西。 一個部件的運動
……
(由於原文被截斷,已翻譯所提供的部分。)