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를 이용하여 상호작용하는 방법에 대해 알아보겠습니다.