scripts/gen_demo_path.py:README 首圖的動畫生成器

用程式產出「terminal 輸入指令 → 圖路徑逐跳亮起」的 SMIL/CSS 動畫 SVG
檔案:scripts/gen_demo_path.py(10KB)· 產出 docs/demo-path.svg

大方向

Graphify README 的第一張圖(docs/demo-path.svg,本站 首頁 也用了)不是手繪的——是這個腳本產生的動畫 SVG。畫面:左邊一個終端機正在輸入 graphify path "FastAPI" "ModelField",右邊同一張圖自己畫出來,一次點亮一跳,其餘節點保持暗淡。

docstring 列了它遵守的約束(這是「怎麼做程式化視覺化」的絕佳教材):

關鍵函數

kt(pairs) · op0() · reveal(t, hold_val, start_val) gen_demo_path.pySMIL 時間軸的三個小工具。

三個 helper 組出 SMIL 的 keyTimes/values:

  • kt(*pairs) — 把 (keyTime, value) 對轉成 valueskeyTimes 字串,且把 0.9500 這種數字收乾淨。
  • op0() — 元素的初始 opacity(STATIC=1 時是 1,bake 靜態 frame 做視覺 QA;否則 0,等動畫亮起)。
  • reveal(t, hold, start) — 產生「在 t 秒亮起 → 保持 → 循環前淡出」的 <animate attributeName="opacity">。這是「路徑逐跳點亮」的核心——每個 hop 有自己的亮起時間,錯開就形成脈衝效果。
主程式:SVG 組裝 + 打字機動畫 gen_demo_path.py檔頭漸層、terminal、游標、圖路徑的產生。

主體是一串 out.append(…) 的組裝:

  • SVG header + defs:背景線性漸層、glow 放射漸層、macOS 三色燈。
  • 終端機打字:把 graphify path "FastAPI" "ModelField" 逐字元輸出,每個字元 reveal(t)t = 0.35 + i*0.058,錯開模擬打字)。
  • 閃爍游標:用 <animate attributeName="x"> 讓游標沿字元位置走、最後原地閃爍。
  • 後面(未全部列出)是右邊圖的節點/邊:每條路徑的節點與邊依序亮起、其餘暗淡——用同樣的 reveal 機制。

執行:python3 scripts/gen_demo_path.py → 寫出 docs/demo-path.svgSTATIC=1 時 bake 靜態 frame。

看完這頁你應該能說出:為什麼用 SMIL 而非 JS、reveal() 怎麼做出「逐跳點亮」、STATIC=1 是幹嘛的。這是一個「把產品 demo 也當成程式化資產」的好例子——圖更新時重跑一次就好,不用手工重畫。

實際產出:assets/demo-path.svg(本站首頁使用,來源標註於 關於)。

① 進階真實情境 Worked Example:把「產品 demo 資產」變成可重生的 CI 產物

前面是「看懂 reveal() 動畫」;這次是把整組行銷/文件圖檔變成 build artifact:你的團隊 README、首頁、說明文件各處都用到圖,圖的資料每次重跑 graphify 後都會變,你不想手工重畫。

  1. 寫一個「重生」腳本:串起 python3 scripts/gen_demo_path.py + 你自製的其它視覺化產生器(社群餅圖、god-node 排行 SVG),統一輸出到 assets/
  2. 接上 CI:graph.json 每次重建後,CI 跑重生腳本——資產永遠跟上最新圖資料,不靠人工重畫。
  3. 用 STATIC 模式做視覺 QASTATIC=1 bake 靜態 frame 進 PR——reviewer 不需開瀏覽器看動畫,直接看靜態圖檢查「路徑對不對、節點位置合不合理」。
  4. 版本化資產:資產 commit 進 repo(像 skill 產物一樣是 generated artifact),出錯時 git diff assets/ 就能看到「哪次資料變化改動了視覺」。
  5. 效能檢查:SVG 走純 SMIL/CSS、無 JS、無外部字型——能在 GitHub sanitizer 與 <img> 下正常顯示,避免「gif/JS 動畫在很多環境被擋」的相容問題。
為什麼選這條路徑:「把 demo 當程式化資產」讓「資料更新 → 圖更新 → 文件更新」一條龍,且每張圖都可稽核(git diff)。相較於手工繪圖,重生的圖保證「與真實現況一致」——這對教學站尤其重要:教程裡的圖講解與實際產出不會脫節。

② 深入原理擴充:SMIL/CSS 動畫的「約束驅動設計」與 reveal() 的時間軸

gen_demo_path.py 真正的價值在「在約束下設計」:

大家以為建圖正確、但其實有誤的案例:以為「docs/demo-path.svg 與 graph.json 是同一份資料的直譯」。其實它是人為挑選的一條 path 的「示意演出」——腳本裡寫死要展示 graphify path "FastAPI" "ModelField" 這條路徑,節點位置、動畫時序都是編排的。它「看起來像」即時渲染,但資料若換了,這條路徑可能不存在、SVG 畫出來的就是假的。真正「即時從圖算路徑」的是 graph.html;SVG 是「劇本」。看 SVG 講解時要記得它是 demo artifact,不是 data snapshot。

③ 診斷式疑難排解表

症狀可能原因解決方案
SVG 在 GitHub 上不顯示動畫用了 JS 或外部資源(sanitizer 會擋)確保全在 SMIL/CSS 內嵌;<animate> 不依賴外部腳本
動畫循環處「跳一下」循環點前沒回到起始狀態,或各 animate 時間軸沒對齊檢查 reveal 的 start 參數;確認所有 dur 都是同一主週期
demo 路徑在圖裡已不存在資料更新後,寫死的 FastAPI→ModelField 這條路徑消失了把 demo 路徑參數化(讀 graph.json 驗證存在再畫);或選一條穩定的主路徑
動畫在部分瀏覽器不跑SMIL 支援度差異(舊 Edge/IE 沒有)接受降級(顯示靜態 frame);或用 CSS keyframes 替代純 SMIL
視覺配色刺眼違反品牌色票(亮粉霓虹)遵守「暗底 + 單一 emerald 強調色」約束;回歸到 #4db18f 家族

④ 進階挑戰題

  1. reveal() 的「亮起 → 保持 → 淡出」三階段。請設計一個「延長後半段停留時間」的變體(例如慢速快照模式),並說明 kt() 的 (keyTime, value) 對該怎麼重新分配。
  2. 「SVG 是劇本、graph.html 是真相」的界線。如果要把 SVG 升級成「自動選路徑」(挑一條介數中心性最高的 path 當主角),需要哪些額外輸入?生成的 SVG 會失去什麼「可預期性」?
  3. SMIL 的 10 秒主週期對所有 animate 是硬約束。若你需要在同一份 SVG 裡加一個「永不淡出」的常亮元素,該怎麼在約束內表達?還是說這個需求本身暗示了約束該被放寬?