Aaron Pham
2025-02-01 fbc45548f7ee80715ec74d8c249c662a26f7feae
feat(graph): enable radial mode (#1738)

3 files modified
18 ■■■■ changed files
docs/features/graph view.md 2 ●●●●● patch | view | raw | blame | history
quartz/components/Graph.tsx 5 ●●●● patch | view | raw | blame | history
quartz/components/scripts/graph.inline.ts 11 ●●●● patch | view | raw | blame | history
docs/features/graph view.md
@@ -36,6 +36,7 @@
    opacityScale: 1, // how quickly do we fade out the labels when zooming out?
    removeTags: [], // what tags to remove from the graph
    showTags: true, // whether to show tags in the graph
    enableRadial: false, // whether to constrain the graph, similar to Obsidian
  },
  globalGraph: {
    drag: true,
@@ -49,6 +50,7 @@
    opacityScale: 1,
    removeTags: [], // what tags to remove from the graph
    showTags: true, // whether to show tags in the graph
    enableRadial: true, // whether to constrain the graph, similar to Obsidian
  },
})
```
quartz/components/Graph.tsx
@@ -18,6 +18,7 @@
  removeTags: string[]
  showTags: boolean
  focusOnHover?: boolean
  enableRadial?: boolean
}
interface GraphOptions {
@@ -39,6 +40,7 @@
    showTags: true,
    removeTags: [],
    focusOnHover: false,
    enableRadial: false,
  },
  globalGraph: {
    drag: true,
@@ -53,10 +55,11 @@
    showTags: true,
    removeTags: [],
    focusOnHover: true,
    enableRadial: true,
  },
}
export default ((opts?: GraphOptions) => {
export default ((opts?: Partial<GraphOptions>) => {
  const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
    const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph }
    const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph }
quartz/components/scripts/graph.inline.ts
@@ -8,6 +8,7 @@
  forceCenter,
  forceLink,
  forceCollide,
  forceRadial,
  zoomIdentity,
  select,
  drag,
@@ -87,6 +88,7 @@
    removeTags,
    showTags,
    focusOnHover,
    enableRadial,
  } = JSON.parse(graph.dataset["cfg"]!) as D3Config
  const data: Map<SimpleSlug, ContentDetails> = new Map(
@@ -161,15 +163,20 @@
      })),
  }
  const width = graph.offsetWidth
  const height = Math.max(graph.offsetHeight, 250)
  // we virtualize the simulation and use pixi to actually render it
  // Calculate the radius of the container circle
  const radius = Math.min(width, height) / 2 - 40 // 40px padding
  const simulation: Simulation<NodeData, LinkData> = forceSimulation<NodeData>(graphData.nodes)
    .force("charge", forceManyBody().strength(-100 * repelForce))
    .force("center", forceCenter().strength(centerForce))
    .force("link", forceLink(graphData.links).distance(linkDistance))
    .force("collide", forceCollide<NodeData>((n) => nodeRadius(n)).iterations(3))
  const width = graph.offsetWidth
  const height = Math.max(graph.offsetHeight, 250)
  if (enableRadial)
    simulation.force("radial", forceRadial(radius * 0.8, width / 2, height / 2).strength(0.3))
  // precompute style prop strings as pixi doesn't support css variables
  const cssVars = [