雖然我在架設的紀錄中提供obsidian-digital-garden的連結但是如果要自己手搓一個跟digital garden模版相似的外觀,必須去研究這位貢獻者 oleeskilddigitalgarden才行。

1. 深度控制條 (Depth Bar)

位置quartz/components/Graph.tsx + graph.inline.ts + graph.scss

  • Graph.tsx 變更

    {localGraph.showDepthSlider && (
      <div class="graph-depth-control">
        <label for="graph-depth-slider" class="depth-label">深度</label>
        <input type="range" id="graph-depth-slider"
               min={localGraph.minDepth || 1}
               max={localGraph.maxDepth || 3}
               value={localGraph.depth || 1} />
        <span class="depth-value">{localGraph.depth || 1}</span>
      </div>
    )}
  • graph.inline.ts 深度控制邏輯

    • 監聽滑桿輸入事件
    • 動態更新所有 graph container 配置
    • 重新渲染但不重置視圖(renderLocalGraph(false)
    • 註冊清理函數避免記憶體洩漏
  • graph.scss 樣式

    • .graph-depth-control 使用 flexbox 佈局
    • 自訂 range input 外觀(18px 圓形 thumb)
    • Webkit 和 Mozilla 瀏覽器兼容
    • Hover 效果與過渡動畫

2. 節點外觀

位置graph.inline.ts

  • A. 當前節點高亮

    if (isCurrent) {
      gfx.circle(0, 0, nodeRadius(n) + 1)
      gfx.stroke({ width: 0.5, color: color(n) })
    }
    • 額外繪製半徑 +1 的圓圈,線寬 0.5,使用節點顏色
  • **B. 節點大小範圍控制:

    function nodeRadius(d: NodeData) {
      const numberOfNeighbours = graphData.links.filter(...).length || 2
      return Math.min(12, Math.max(numberOfNeighbours / 1.5, 3))
    }
    • 最小 3,最大 12,根據連結數量動態調整
  • C. 標籤始終可見

    const label = new Text({
      alpha: 1, // 從 0 改為 1
      anchor: { x: 0.5, y: 0 }, // 從 {x: 0.5, y: 1.2} 改變
      style: {
        fontSize: 8, // 從 fontSize * 15 改為固定 8
        fontFamily: "Sans-Serif" // 從 bodyFont 改變
      }
    })
    label.y = nodeRadius(n) + 3 // 新增:標籤位置在節點下方
  • D. 連線變細

    stroke({ alpha: l.alpha, width: 0.5, color: l.color }) // 從 width: 1 改為 0.5
  • E. 移除標籤動態透明度

    // 所有標籤始終保持完全不透明
    for (const label of labelsContainer.children) {
      label.alpha = 1
    }

3. zoomToFit 功能

在每頁渲染的時候會自動將graph縮放至符合畫面大小

位置graph.inline.ts

完整實現

if (shouldZoomToFit && graphData.nodes.length > 0) {
  setTimeout(() => {
    // 1. 計算所有節點的邊界框
    let minX = Infinity, maxX = -Infinity
    let minY = Infinity, maxY = -Infinity
   
    for (const node of graphData.nodes) {
      if (node.x !== undefined && node.y !== undefined) {
        minX = Math.min(minX, node.x)
        maxX = Math.max(maxX, node.x)
        minY = Math.min(minY, node.y)
        maxY = Math.max(maxY, node.y)
      }
    }
   
    // 2. 計算適當的縮放比例
    const padding = 40
    const graphWidth = maxX - minX
    const graphHeight = maxY - minY
    const scaleX = (width - padding * 2) / (graphWidth || 1)
    const scaleY = (height - padding * 2) / (graphHeight || 1)
    const scale = Math.min(scaleX, scaleY, 4) // 最大 4x
   
    // 3. 計算圖表中心點
    const centerX = (minX + maxX) / 2
    const centerY = (minY + maxY) / 2
   
    // 4. 計算平移量,讓圖表中心對齊畫面中心
    const translateX = width / 2 - (centerX + width / 2) * scale
    const translateY = height / 2 - (centerY + height / 2) * scale
   
    // 5. 應用變換(200ms 平滑過渡)
    currentTransform = zoomIdentity
      .translate(translateX, translateY)
      .scale(scale)
   
    select<HTMLCanvasElement, NodeData>(app.canvas)
      .transition()
      .duration(200)
      .call(zoomBehavior.transform, currentTransform)
  }, 200)
}

📊 參數變更對照表

參數原值新值用途
linkDistance (local)30100增加節點間距
showTagstruefalse隱藏標籤節點
centerForce (global)0.20.3加強中心引力
enableRadial (global)truefalse禁用徑向佈局
scaleExtent[0.25, 4][0.1, 10]擴大縮放範圍
nodeRadius2 + √linksmin(12, max(n/1.5, 3))控制節點大小
label.alpha動態1標籤始終可見
label.fontSizefontSize*158固定字體大小
link.width10.5連線變細

新增參數

參數預設值說明
showDepthSlidertrue顯示深度控制條
minDepth1最小深度
maxDepth3最大深度
initialZoom1.5 (local) / 1 (global)初始縮放級別
linkStrength1連結強度

研究原模版以及套用感謝Cursor😎!死啃程式碼找到想對應的配置可能會花我好幾個小時吧🫠 以上資料整理依靠git diff以及Claude👍