- 발행일
JSTree & VTree 사전 조사 — 그리고 1년 뒤 무엇이 살아남았나
JSTree & VTree 사전 조사 — 그리고 1년 뒤 무엇이 살아남았나
교통 데이터 플랫폼에서 트리 구조(메타 클래스 계층, ASN.1, 개념 그래프)를 시각화해야 해서 두 라이브러리를 조사하고 붙였던 기록입니다. 당시의 조사 내용을 두고, 1년 넘게 운영한 뒤의 결말과 당시 코드에서 지금 보면 걸리는 지점을 뒤에 붙였습니다.
1. 두 라이브러리의 성격
| jsTree | VTree | |
|---|---|---|
| 기반 | jQuery 플러그인 | D3 래퍼 |
| 잘하는 것 | 깊은 뎁스의 목록형 탐색 — 접기/펼치기, 선택, 아이콘 | 관계의 시각적 표현 — 노드를 공간에 배치 |
| 데이터 | {text, children} JSON | 일반 JSON을 reader로 변환 |
2. jsTree 기본 사용
Jinja 템플릿에서 서버 데이터를 받아 초기화하는 형태로 썼습니다.
$(`#{{ jstree_id }}`).jstree({
core: {
data: jsonObj,
themes: { name: 'proton', responsive: true },
},
types: {
default: { icon: 'far fa-folder text-primary' },
file: { icon: 'far fa-file text-primary' },
},
plugins: ['types'],
});
// 노드 선택 → 해당 클래스의 속성 테이블을 AJAX로 조회
$('#classTree').on('select_node.jstree', (e, data) => {
showMetaClassTable(data.node.id, data.node.text);
});
자주 쓴 이벤트만 추리면:
| 이벤트 | 시점 | 용도 |
|---|---|---|
select_node.jstree | 노드 클릭/선택 | 상세 조회 트리거 — 가장 많이 씀 |
changed.jstree | 선택 집합 변경 | 다중 선택 감지 |
open_node.jstree / close_node.jstree | 펼침/접힘 | 지연 로딩, 상태 저장 |
loaded.jstree | 초기 로드 완료 | open_all, 첫 노드 자동 선택 |
refresh.jstree | .refresh() 호출 | 새로고침 버튼 연동 |
기본 사용법과 모달 연동은 별도 글로 정리했습니다: Jstree 기본2025-04-23jsTree 기본 사용법 — 플러그인·API 정리와 실무에서 걸린 지점jQuery 기반 트리 플러그인 jsTree의 플러그인·API·이벤트를 표로 정리한 레퍼런스입니다. 실무 트리 화면에 붙이면서 걸린 지점들을 뒤에 덧붙였습니다 — check_callback의 진짜 의미, 루트 노드 id가 #이라는 함정, 기본 contextmenu를 버리고 모달로 간 이유까지. · 컨텍스트 메뉴와 모달2026-01-16JSTree 플러그인 활용과 사용자 정의 컨텍스트 메뉴JSTree의 주요 플러그인 설정 방법과 contextmenu 플러그인으로 사용자 정의 우클릭 메뉴(추가/편집/삭제/복사)를 구현하는 방법을 정리합니다.
3. VTree 사용
const vt = new VTree(vtreeEl);
vt.width(width).height(height);
const reader = new VTree.reader.Object(); // 일반 JSON → VTree 구조로 변환
vt.data(reader.read(jsonData)).update();
브라우저 크기를 재서 영역을 잡고 reader로 JSON을 변환해 렌더하는 구조입니다. 조사 시점의 판단은 "jsTree는 깊은 뎁스, VTree는 관계 표현"이라는 분업이었습니다.
4. 1년 뒤 — 무엇이 살아남았나
지금 코드베이스를 검색하면 결말이 명확합니다.
- jsTree: 생존, 오히려 확대. 메타 클래스 트리, OID 트리, 인스턴스 트리 등 10개 이상의 화면에서 트리 탐색의 기본이 됐습니다. 인라인 스크립트였던 초기 코드는 ES 모듈(
metaClassTree.js+ 공용 유틸)로 재편됐고요. - VTree: 완전 제거.
new VTree는 코드베이스에 한 줄도 없습니다. 관계 시각화는 전부 D3 직접 구현으로 갔어요 — 관계도 연쇄 탐색, 선버스트, 데이터 맵, 워드클라우드.
갈린 기준을 지금 말로 정리하면 이렇습니다.
정형화된 상호작용(목록형 트리 탐색)은 라이브러리가 이기고, 표현 자체가 요구사항인 화면(관계 시각화)은 래퍼가 진다.
jsTree가 이긴 이유는 트리 탐색의 요구사항이 어느 프로젝트나 비슷해서입니다 — 접고 펼치고 선택하고. 반면 관계 시각화는 화면마다 요구가 달랐습니다. 연쇄 탐색은 노드 겹침을 힘 시뮬레이션으로 풀어야 했고2026-07-23노드 두 개가 땅콩처럼 붙었다 — 연쇄 탐색 관계도와 forceCenter클래스 3개짜리 화면이 18개 노드로 그려지던 관계도를, 백엔드를 안 건드리고 중심 이동 탐색으로 바꿨습니다. 배치를 힘 시뮬레이션에서 고정 방사 좌표로 뺐더니 이번엔 노드 두 개가 땅콩처럼 붙었어요. 범인은 forceCenter였고, 고정 노드가 있는 그래프에서 왜 그게 겹침을 만드는지 정리했습니다., ASN.1 트리는 Graphviz에서 D3로 옮기며2026-04-19ASN.1 트리 렌더링을 graphviz PNG에서 D3.js로 갈아탄 이유서버에서 graphviz로 PNG를 그려 내려주던 ASN.1 트리를 D3.js 기반 Collapsible Tree로 전환한 과정과 그 이유를 정리합니다. 서버 의존성 제거, 인터랙션, JSON 스키마 설계까지. 렌더링을 처음부터 설계했어요. VTree 같은 래퍼는 이 요구들 앞에서 "래퍼가 지원하는 것까지만 가능"이라는 천장이 됐고 어차피 D3를 배워야 한다면 래퍼를 끼울 이유가 없었습니다.
5. 당시 코드에서 지금 보면 걸리는 것
사전 조사 단계의 코드라 지금 기준으로는 손볼 게 많았고 실제로 대부분 이후에 고쳐졌습니다. 뭐가 문제였는지 남겨둡니다.
- 노드의 표시 텍스트를 URL 식별자로 썼습니다.
location.href = `/…/concept_graph/${선택 노드의 text}/`— 이름이 중복되거나 특수문자가 들어가면 깨집니다. 화면에 보이는 문자열과 리소스 식별자는 다른 것인데 섞어 썼어요. 현재 코드는node.id기반으로 바뀌었습니다. - 서버 JSON을 템플릿 리터럴로 주입했습니다.
JSON.parse(`{{ data | safe }}`)— 데이터에 백틱이나${가 들어오는 순간 스크립트가 통째로 깨지고,| safe라 이스케이프도 없으니 XSS 표면이기도 합니다. Django에는 정확히 이 용도의json_script필터가 있고 그게 정석입니다. - 초기화를 두 번 하는 줄에 "완전 초기화 마무리"라는 주석을 달아놨습니다.
.on(…).jstree()— 이미 초기화된 트리에 인자 없이 재호출하면 아무 일도 안 일어납니다. 동작을 이해 못 한 채로 "되니까 두는" 코드에 그럴듯한 주석까지 붙인 사례라, 지금 보면 제일 뜨끔한 부분이에요. - 이벤트 17종 전부에 console.log를 바인딩한 디버그 코드가 그대로 실려 있었고 AJAX 결과를 문자열 연결 innerHTML로 그려서 서버 텍스트가 이스케이프 없이 DOM에 들어갔습니다.
- 응답 필드명에
is_mandatary오타가 있었습니다(테이블에 undefined로 렌더될 자리). 현재 코드베이스에는 이 오타가 0건 — 고쳐졌습니다. - 원문의 VTree "공식 문서" 링크는 사실 d3-selection 이벤트 문서였습니다. 조사 글의 참고 링크가 잘못돼 있으면 조사 자체의 신뢰가 흔들리는데, VTree가 D3 래퍼다 보니 뒤섞어 적은 거였어요.
돌아보면
- 사전 조사의 답은 조사가 아니라 운영이 알려줬습니다. "jsTree는 뎁스, VTree는 관계"라는 당시 결론에서 앞쪽만 살아남았어요. 래퍼 라이브러리를 고를 때는 "지금 되는가"보다 "요구가 래퍼의 천장을 넘으면 어떻게 되는가"를 물었어야 했습니다.
- 한계 — jsTree도 무비용이 아닙니다. jQuery 의존이 그대로 남았고 노드 수천 개 규모에서의 렌더 성능은 검증하지 않았습니다(현재 화면들은 수백 노드 수준). jQuery 없는 대안(가상 스크롤 트리 등)으로의 이전은 필요가 생기기 전까지는 하지 않기로 한 상태입니다.