Introduction
d3-selection은 데이터를 기반으로 DOM을 조작할 수 있는 다양한 기능을 제공합니다.
이번 시리즈는 d3-force와 연계하여 Github Follower, Following 관계망 그리기를 목표로 하고 있으므로 구현 과정에서 필요한 부분에 대해서만 다루도록 하겠습니다.
들어가기 전에 Selection 객체에 대해 알아봅시다. d3-selection으로 선택한 요소는 groups와 parents property를 갖는 Selection 객체를 생성합니다. Selection 객체는 선택한 요소를 제어할 수 있는 다양한 메서드를 제공합니다.
- 요소 선택: selection, select, selectAll, selectChild, selectChildren, filter, merge
- 요소 변경: attr, classed, style, property, text, html, append, insert, remove, clone, sort, order, raise, lower
- 데이터 연결: data, join, enter, exit, datum
- 이벤트 핸들링: on, dispatch
- 제어 흐름: each, call, empty, nodes, size, [Symbol.iterator]()
d3-selection을 이용하여 선택한 요소에 데이터를 연결하고 그 데이터를 기반으로 요소를 제어할 수 있습니다.
이제 코드와 함께 d3-selection을 이용하여 간단한 그래프를 그려보겠습니다.
Creating and Editing
관계망에 사용할 nodes와 links를 브라우저에 그려주기 위해 d3-selection으로 svg를 생성하고 크기를 설정해줍니다.
const root = d3
.select('body') // === d3.select(document.body)
.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.style('display', 'block');body 요소를 선택하고 하위에 svg 요소를 추가했습니다. 그리고 attr 메서드와 style 메서드로 svg의 속성과 스타일을 지정했습니다. root엔 svg 요소를 담고 있는 Selection 객체가 할당됩니다.
root 아래 links와 nodes를 각각 묶을 g 요소를 생성합니다. (linkGroup을 먼저 생성해야 뒤에 배치됩니다.)
const linkGroup = root.append('g').attr('id', 'links');
const nodeGroup = root.append('g').attr('id', 'nodes');id는 별 다른 역할 없이 식별을 위해 추가해주었습니다.
Joining Data
이제 DOM 요소에 데이터를 연결해봅시다. nodeGroup 아래 circle을 배치하고 nodes 데이터를 연결합니다.
const circles = nodeGroup
.selectAll('circle')
.data(nodes)
.join('circle')
.attr('r', 5)
.attr('fill', 'blue');얼핏 보면 말이 되는 듯하지만 아직 생성도 하지 않은 circle을 왜 선택하고 있는지 의문이 생깁니다. 각 단계가 생성하는 Selection 객체를 보고 흐름을 이해해봅시다.
nodeGroup.selectAll('circle')은 당연히 빈 Selection 객체를 반환합니다.
💡 당연히 비어있는
Selection객체를 받을건데 왜selectAll을 쓸까요?이후에
nodes를 추가해야 할 상황이 생긴다면, 기존에 생성되었던circle요소와nodes를 비교하여 추가된node의 개수만큼circle요소를 추가합니다. 즉, 비교를 위해 선택하는 요소와 생성하는 요소는 동일해야 합니다.
_groups: [NodeList(0)]
_parents: [g#nodes].data(nodes)는 빈 Selection 객체에 nodes 배열의 길이만큼 연결합니다. jt 객체는 __data__ property에 node 정보를 갖고 있습니다.
_enter: [Array(5)]
0: (5) [jt, jt, jt, jt, jt]
_exit: [Array(0)]
0: []
_groups: [Array(5)]
0: (5) [empty × 5]
_parents: [g#nodes]_enter: Array(1)
0: Array(5)
0: jt {...}
1: jt {...}
2: jt {...}
3: jt {...}
4: jt
namespaceURI: "http://www.w3.org/2000/svg"
ownerDocument: document
__data__: {id: 5, index: 4, x:...}
_exit: [Array(0)]
_groups: [Array(5)]
_parents: [g#nodes].join('circle')은 .enter().append('circle')과 동일한 단축 표현입니다. .enter()까지 실행한 결과는 아래와 같습니다.
_groups: Array(1)
0: (5) [jt, jt, jt, jt, jt]
_parents: [g#nodes]_groups: Array(1)
0: Array(5)
0: jt {ownerDocument: document, namespaceURI: 'http://www.w3.org/2000/svg', ...}
1: jt {ownerDocument: document, namespaceURI: 'http://www.w3.org/2000/svg', ...}
2: jt {ownerDocument: document, namespaceURI: 'http://www.w3.org/2000/svg', ...}
3: jt {ownerDocument: document, namespaceURI: 'http://www.w3.org/2000/svg', ...}
4: jt
namespaceURI: "http://www.w3.org/2000/svg"
ownerDocument: document
__data__: {id: 5, index: 4, x: ...}
...
_parents: [g#nodes]enter().append('circle')까지 실행한 결과는 아래와 같습니다.
_groups: Array(1)
0: Array(5)
0: circle
1: circle
2: circle
3: circle
4: circle
__data__: {id: 5, index: 4, x:...}
...
_parents: [g#nodes]circle 요소가 연결되었고 circle 내부에 __data__가 결합되었습니다.
Impl.
ticked 함수 내부에서 DOM 요소와 결합한 node 객체의 x, y 좌표 데이터를 이용해 요소를 화면 상에 그려보았습니다.
See the Pen D3 Force (w/ SVG) by Park, Jinyong (@jinyongp) on CodePen.
Conclusion
d3-selection의 개념과 데이터 결합 방법과 흐름에 대해 살펴보았습니다. 저번과 달리 많은 개념에 대해 알아보진 않았지만, 요소를 선택하고 속성을 변경하고 데이터를 결합하는 정말 필요한 내용만 다루면서 d3-force와 연동하여 간단한 그래프를 그려볼 수 있었습니다.
node의 데이터가 많아질수록 d3-selection을 더욱 적극적으로 이용할 예정이므로, 그 때를 위해 이번엔 이 정도로 하고 다음 시리즈에선 d3-zoom과 d3-drag를 이용하여 상호작용하는 방법에 대해 알아보겠습니다.