Data Driven Transformation of DOM

d3.js
Written on Apr 16, 2022


Introduction

d3-selection은 데이터를 기반으로 DOM을 조작할 수 있는 다양한 기능을 제공합니다.

이번 시리즈는 d3-force와 연계하여 Github Follower, Following 관계망 그리기를 목표로 하고 있으므로 구현 과정에서 필요한 부분에 대해서만 다루도록 하겠습니다.

들어가기 전에 Selection 객체에 대해 알아봅시다. d3-selection으로 선택한 요소는 groupsparents 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

관계망에 사용할 nodeslinks를 브라우저에 그려주기 위해 d3-selection으로 svg를 생성하고 크기를 설정해줍니다.

Language:javascript
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의 속성과 스타일을 지정했습니다. rootsvg 요소를 담고 있는 Selection 객체가 할당됩니다.

root 아래 linksnodes를 각각 묶을 g 요소를 생성합니다. (linkGroup을 먼저 생성해야 뒤에 배치됩니다.)

Language:javascript
const linkGroup = root.append('g').attr('id', 'links');
const nodeGroup = root.append('g').attr('id', 'nodes');

id는 별 다른 역할 없이 식별을 위해 추가해주었습니다.


Joining Data

이제 DOM 요소에 데이터를 연결해봅시다. nodeGroup 아래 circle을 배치하고 nodes 데이터를 연결합니다.

Language:javascript
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 요소를 추가합니다. 즉, 비교를 위해 선택하는 요소와 생성하는 요소는 동일해야 합니다.

Language:javascript
_groups: [NodeList(0)]
_parents: [g#nodes]

.data(nodes)는 빈 Selection 객체에 nodes 배열의 길이만큼 연결합니다. jt 객체는 __data__ property에 node 정보를 갖고 있습니다.

Language:javascript
_enter: [Array(5)]
  0: (5) [jt, jt, jt, jt, jt]
_exit: [Array(0)]
  0: []
_groups: [Array(5)]
  0: (5) [empty × 5]
_parents: [g#nodes]
Language:javascript
_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()까지 실행한 결과는 아래와 같습니다.

Language:javascript
_groups: Array(1)
  0: (5) [jt, jt, jt, jt, jt]
_parents: [g#nodes]
Language:javascript
_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')까지 실행한 결과는 아래와 같습니다.

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

References