0014. 그래프 보기는 force-graph를 React 래퍼 없이 쓰고, 그래프 모드의 사이드바는 루트 칸 목록으로 둔다
- 상태: 채택
- 날짜: 2026-09-29
- 관련: src/renderer/src/workspace/GraphView.tsx, src/renderer/src/workspace/GraphSlot.tsx, src/renderer/src/features/sidebar/GraphRootList.tsx, src/shared/linkGraph.ts, vasturiano/force-graph, 옵시디언 그래프 보기
옵시디언 같은 그래프 보기를 만든다. 연결 폴더(프로젝트 루트)마다 그 안의 .md 파일이 노드, 파일 사이의 링크가 선이다. 노드와 선 목록은 window.graph.build(root)가 만든다(PR #61).
그래프 화면에는 확대와 축소, 화면 이동, 노드 끌기, 마우스를 올린 노드와 이웃의 강조, 노드 누르기가 필요하다. 배치는 힘 기반이라 연결된 노드끼리 모여야 하고, 옵시디언의 힘 설정(Center, Repel, Link force, Link distance)과 같은 손맛이면 좋다. 저장소 하나에 .md 파일이 2~3천 개까지 있을 수 있다.
그래프는 편집기 영역의 탭으로 연다. 이 앱의 탭은 숨긴 채 DOM에 남겨 두고, 보일 때만 드러낸다.
- 그래프 화면은
force-graph로 그리고, React 래퍼(react-force-graph-2d) 없이 인스턴스를 직접 다룬다.useEffect에서new ForceGraph(element)로 만들고 언마운트할 때_destructor()로 정리한다. 화면 모듈은React.lazy로 그래프 탭을 처음 열 때 불러온다. - 힘 설정은 코드의 상수로 둔다.
d3Force('charge')와d3Force('link')에 상수를 넣고, 설정 화면에 여는 일은 하지 않는다. - 그래프 모드의 사이드바는 트리 대신 루트 칸 목록을 보여 준다. 레일에 그래프 아이콘을 더하고, 그래프 모드에서는 연결 폴더마다 이름과
.md파일 수만 적힌 칸을 둔다. worktree, 파일, git 트리와 우클릭 메뉴는 없다. 사이드바의 모양이 모드에 따라 달라지는 것은 이 모드가 처음이다. 머리(제목과 프로젝트 추가 단추)는 다른 모드와 같다.
근거 / 검토한 대안
섹션 제목: “근거 / 검토한 대안”force-graph를 고른 이유: 확대, 이동, 노드 끌기, 올리기와 누르기 판정이 기본으로 들어 있다. 배치 엔진이 d3-force라 옵시디언과 같은 힘(중심, 반발, 선의 당김과 길이)을 그대로 쓴다. 캔버스로 그려 2~3천 노드도 충분히 돈다. MIT이고 gzip 약 57KB다.- React 래퍼를 쓰지 않는 이유: 래퍼(react-kapsule)는 React가 fiber를 다시 쓸 때 인스턴스를 다시 만들지 않는 문제가 있다(vasturiano/react-force-graph#596). 이 앱은 탭을 숨긴 채 유지하므로 그 경우를 만날 수 있다. 직접 다루면 만들기, 크기 맞추기, 멈추기와 다시 돌기, 정리를 탭의 생애에 맞춰 부를 수 있다.
d3-force를 직접 쓴다. 의존성은 가장 작다. 그러나 캔버스 그리기, 확대와 이동, 끌기, 올리기 판정을 모두 새로 만들어야 한다.sigma와graphology를 쓴다. WebGL이라 수만 노드에도 빠르다. 그러나 이 앱의 규모(2~3천 노드)에는 캔버스로 충분하고, 힘 배치는 따로 붙이는 ForceAtlas2라 옵시디언의 d3-force 설정과 손맛이 다르다. 라이브러리도 둘을 들여야 한다.cytoscape를 쓴다. 그래프 분석과 배치 종류가 많지만 무겁고, 이번에 필요한 것(힘 배치 하나와 기본 상호작용)에 비해 크다.- 사이드바에 트리를 그대로 두고 그래프 단추만 더한다. 모양은 한 가지로 유지된다. 그러나 그래프는 루트 단위라 worktree, 파일, git 트리가 쓸 데가 없고, 루트마다
.md파일 수를 보여 주는 편이 어떤 그래프를 열지 고르기 쉽다.
- 올리기 강조가 엔진이 멈춘 뒤에도 보이도록
autoPauseRedraw(false)로 계속 그린다. 그래프 탭이 보이는 동안은 매 프레임 캔버스를 다시 그리고, 탭이 숨으면pauseAnimation()으로 멈춘다. force-graph의 올리기 판정용 캔버스는 800ms 간격으로만 다시 그려진다(vasturiano/force-graph#366). 노드가 움직이는 동안에는 올리기 판정이 잠깐 늦을 수 있다.- 그래프 탭은 보일 때마다 그래프를 다시 받는다. 같은 id의 노드는 앞 배치의 자리를 이어 받아 흩어지지 않는다.
- 그래프 화면 파일(
GraphSlot,GraphView,graphStyle)은features/graph/가 아니라workspace/에 둔다.PaneTree가 탭 자리를 그리므로 ADR 0008의 renderer 의존 방향에서 workspace가 import할 수 있는 자리여야 한다. 루트 칸 목록은 쓰는 곳이 사이드바뿐이라features/sidebar/에 둔다. - 힘 설정을 사용자가 고칠 수 있게 하거나, 무리를 색으로 나누는 일은 나중에 따로 한다.