발행일

OID 트리를 jsTree에서 조직도로 갈아탔다 — 네 화면 중 두 곳만

OID 트리를 jsTree에서 조직도로 갈아탔다

OID는 점으로 이어 붙인 계층 식별자입니다. 1.3.6.1.4.1 같은 모양이고, 각 자리가 번호로 갈라지는 가지예요.

이걸 jsTree로 그리고 있었습니다. 이 블로그에 jsTree는 살아남고 VTree는 걷어냈다는 글을 쓴 적이 있는데, 그 판단은 지금도 대체로 맞습니다. 트리 화면 대부분은 여전히 jsTree예요. 다만 OID 화면에서는 안 맞았습니다.

세로 목록으로 펼치면 형제가 번호 순으로 늘어서 있다는 게 안 보입니다. 들여쓰기 깊이는 보이는데 같은 단에 몇 개가 있는지, 그중 지금 보는 게 몇 번째인지는 세로로 훑어야 알아요. 계층이 다섯 단만 되면 화면을 위아래로 굴리면서 머릿속으로 다시 그려야 합니다.

무엇으로 그릴 것인가

방법장점포기하는 것판단
① jsTree 유지변경 0. 다른 트리 화면과 조작이 같음형제 순서·계층 폭이 안 읽히는 게 그대로기각
② jsTree 테마·플러그인으로 가로 배치기존 이벤트·API를 씀jsTree는 세로 목록 렌더러다. 배치를 바꾸는 건 사실상 재구현이고 API만 남음불가
③ 기존 D3 관계도(d3-graph-common) 재사용이미 있는 코드힘 배치라 형제 순서를 표현할 자리가 없음기각
d3.hierarchy + tree()로 SVG 조직도 신설형제가 번호 순으로 좌→우. 계층 폭이 한눈에렌더러 하나를 새로 짊어짐채택

③을 기각한 이유가 구조 뷰어를 따로 만든 이유와 같습니다. 힘 시뮬레이션은 "무엇과 이어져 있나"에 답하는 도구라 순서가 있는 것을 순서대로 놓지 못합니다. OID는 순서가 곧 의미예요.

d3.tree()nodeSizeseparation을 주고, 노드 폭은 텍스트를 실측해 정합니다. 이름 길이가 제각각이라 고정 폭으로는 글자가 잘리거나 여백이 남아요.

네 화면 중 두 곳만 바꿨다

OID 트리를 쓰는 화면은 넷입니다. 공개 OID 검색, 관리 OID 목록, 심사 상세, 신청 상세.

두 곳만 바꿨습니다. 공개 검색과 관리 목록만 조직도가 되고, 심사·신청 상세는 jsTree로 남았어요.

가른 기준은 경로 바가 있느냐입니다.

조직도는 드릴다운을 쓰는데, 드릴다운은 선택한 노드를 뿌리로 삼고 그 위를 잘라냅니다. 잘린 상위를 어딘가에 보여주지 않으면 사용자가 지금 어디 있는지 모릅니다. 그래서 경로 바(breadcrumb)가 필수예요.

심사·신청 상세는 화면 안에 트리가 곁들여 들어가는 자리라 경로 바를 놓을 데가 없습니다. 거기서는 전체를 펼쳐 보여주는 세로 목록이 오히려 맞아요.

"새 렌더러를 만들었으니 전부 갈아탄다"가 아니라 화면이 그걸 받을 구조인지를 먼저 본 셈입니다. 조직도 화면만 d3를 싣도록 스크립트 블록에 플래그(use_chart)를 하나 뒀습니다 — 안 쓰는 화면이 라이브러리를 내려받을 이유가 없으니까요.

뿌리를 옮기고, 잘린 위쪽은 경로로 남긴다

자식이 있는 노드를 고르면 그 노드가 새 뿌리가 됩니다. 잘려 나간 상위는 경로 칩으로 남아요.

규칙 하나를 뒀습니다 — 잎은 뿌리로 삼지 않습니다. 검색이 잎을 맞히면 한 단계 위를 뿌리로 그리고 잎은 선택만 합니다. 잎을 뿌리로 만들면 자식이 없으니 화면에 노드가 하나만 뜨고, 그건 트리가 아니라 그냥 카드거든요.

위치는 ?tree_root=에 남깁니다. 삭제하고 새로고침해도 보던 자리가 유지돼요. 상위 OID·참조 OID를 눌렀을 때도 페이지를 새로 열지 않고 트리를 그 자리로 옮깁니다(href는 대체 경로로 남겨 새 탭 열기가 되게 뒀습니다).

편집 중에는 경로 이동을 막았습니다. 이건 편집 중 트리를 누르면 입력이 사라지던 문제와 같은 자리인데, 조직도에서도 같은 방어를 이식해야 했어요. 렌더러를 바꿔도 사용자가 하는 실수는 안 바뀝니다.

12,691px 짜리 SVG

드릴다운을 붙이고 나서 최상위 화면이 이상해졌습니다. 부모 노드가 화면 밖으로 밀려나 있었어요.

드릴다운이 조상만 자르고 하위는 전부 그리고 있었습니다. 잎이 42개인 가지가 하나 있었는데, 그 가지 하나가 모든 상위 화면의 폭을 정해버립니다. SVG 폭이 12,691px이 됐고, 가로 스크롤 한가운데 어딘가에 뿌리가 있었습니다.

// 트리 전체를 늘 다 그리면 잎이 42개인 가지 하나가 모든 상위 화면의 폭을
// (…) 잎만 있는 가지는 자르는 대신 격자로 접어 폭을 화면 안에 가둔다.

두 가지로 잡았습니다.

아래 단부터 접습니다. 컨테이너 폭이 배치의 예산이고, 들어올 때까지 가장 깊은 단부터 접어 올라갑니다. 접힌 부모에는 +N 배지를 붙여 몇 개가 숨었는지 보여줘요.

잎만 있는 가지는 격자로 눕힙니다. 자식이 전부 잎이고 한 줄로는 넘치면 여러 줄 격자로 접습니다(공개 4열·관리 3열).

작은 것들이 재미있었습니다.

// 폭을 재는 시점엔 상자가 비어 있어 세로 스크롤바가 없다. 그리고 나서 생기면 그만큼
// 좁아져 가로 스크롤이 따라 붙는다 — 미리 빼 둔다.
...
// 접힌 자식 수 배지 — 아래에 띄우면 상자가 잘려 나간 것처럼, 깊이 겹치면 노드에 들러붙은 것처럼
// 보인다. 살짝만 걸치고 흰 링으로 노드 채움과 떼어 놓는다.

그리고 가로 중심 기준을 그림 외곽에서 뿌리의 세로축으로 바꿨습니다. 외곽 기준으로 가운데를 맞추면 한쪽에 넓은 가지가 있을 때 뿌리가 옆으로 밀려요. 사람이 보는 중심은 그림의 무게중심이 아니라 뿌리입니다.

선택 노드로 스크롤하는 것도 가운데 정렬에서 최소 이동으로 바꿨습니다. 이미 보이는 노드를 굳이 가운데로 끌어오면 화면이 흔들려서, 그때마다 어디를 보고 있었는지 다시 찾아야 하거든요.

폐지된 OID는 배지만 빨개서는 안 된다

폐지(DLT) OID를 켜고 끄는 토글을 붙였습니다. 기본은 등록(ACT)분만 보이고, 켜면 폐지도 나와요. 선택은 ?retired=1에 남겨 새로고침에도 유지되고, 토글 후에도 보던 드릴다운 위치와 선택 노드를 지킵니다.

색을 정할 때 짚은 게 있습니다.

- 폐지 노드는 rose 계열로 구분 — 트리는 점선 테두리, 식별자·표기법 카드도 함께 전환
  (배지만 빨갛고 카드가 파랗게 남으면 아직 쓸 수 있는 값으로 읽힌다)

트리 노드만 빨갛게 하고 오른쪽 상세 카드는 평소 색으로 두면, 카드를 보는 순간 "아직 쓸 수 있는 값"으로 읽힙니다. 상태 표시는 그 상태의 데이터가 보이는 모든 자리에 같이 가야 합니다.

심사 중 상태는 어느 쪽에도 안 넣었습니다. 등록도 폐지도 아닌 것을 둘 중 한쪽에 끼워 넣으면 그 토글의 뜻이 흐려져요.

지금 뿌리 위에 유령 하나

드릴다운을 하고 나면 위쪽 맥락이 경로 칩으로만 남습니다. 글자로는 있는데 그림에는 없어요.

현재 뿌리 위에 직속 상위 하나를 투명한 점선 노드로 얹었습니다. 누르면 그 자리로 드릴업하고, 기존 선택 핸들러를 그대로 씁니다.

형제는 안 그립니다. 위쪽 한 칸만 보여주는 게 목적이라 형제까지 그리면 그건 드릴업이 아니라 그냥 한 단계 위 화면이 됩니다.

계산에서는 빼야 했습니다.

// 색 램프·data-oid-depth 셈에서 유령 제외(branchDepth, 유령은 -1)

깊이별 파랑 램프를 쓰는데 유령을 0단으로 세면 그 아래 전부가 한 칸씩 밀려 색이 달라집니다. 드릴다운할 때마다 같은 노드의 색이 바뀌는 거예요. 유령은 -1로 두고 램프 계산에서 제외했습니다.

검증

  • 공개 검색 9건·관리 목록 6건, e2e 시나리오 2개로 드릴다운·경로 복귀·선택 유지를 잠금.
  • 잎 42개짜리 가지가 있는 최상위 화면에서 가로 스크롤이 안 생기는 것 확인. 고치기 전 SVG 폭 12,691px을 먼저 재고 시작했습니다.
  • 폭 초과 접기와 격자 배치 시나리오 2건 추가.
  • 창 폭을 줄여 가며 접히는 단이 늘어나는 것 확인.
  • 폐지 토글을 켠 뒤 드릴다운 위치·선택 노드가 유지되는 것 확인.
  • 드릴다운을 반복해도 같은 노드의 색이 안 바뀌는 것 확인(유령 제외가 안 되면 바뀝니다).
  • 조직도를 안 쓰는 심사·신청 상세에서 d3가 안 실리는 것 확인.

남은 것 · 한계

  • 같은 데이터에 UI가 두 벌입니다. 두 화면은 조직도, 두 화면은 jsTree라 조작이 다릅니다. 원칙(경로 바 유무)은 있지만 사용자가 그 원칙을 알 리가 없어요. 심사 화면에서 트리를 만진 사람이 검색 화면에 오면 다시 배웁니다.
  • ?tree_root=에 pk를 씁니다. 바로 앞에 쓴 데이터 맵 글에서는 "pk는 재적재에 밀리니 주소에 넣지 말자"고 해놓고, 여기서는 pk를 넣었습니다. OID 노드는 재적재 대상이 아니라 사정이 다르긴 한데, 같은 주에 반대 결정을 내렸다는 사실은 그대로고 어느 쪽 규칙이 기본인지는 안 정했습니다.
  • 접힘 상태가 주소에 안 남습니다. 폭 기준으로 접히니까 창 크기가 다르면 링크를 받은 사람은 다른 그림을 봅니다.
  • 격자 열 수(공개 4·관리 3)가 고정값입니다. 폭을 재서 접으면서 열 수만 상수인 건 앞뒤가 안 맞아요. 좁은 창에서는 4열 격자가 다시 넘칠 수 있습니다.
  • 세로 스크롤바 폭을 미리 빼는 건 추정입니다. 잴 시점에 아직 안 생긴 것을 예상해 빼는 거라, 실제로 안 생기는 경우엔 그만큼 공간이 남습니다.
  • 유령 노드는 하나뿐입니다. 다섯 단 들어간 상태에서 위쪽 맥락은 여전히 경로 칩 글자에 의존해요.

관련 글: JSTree & VTree 사전 조사 — 그리고 1년 뒤 무엇이 살아남았나 · 데이터 맵에서 세 단계 들어간 자리를 주소에 남겼다