D3.js intro

December 12, 2018 · 3 mins to read

이직한지 2주째… 얌전히 눈치를 보며 팀의 안살림을 하다가, 뜬금없이 D3.js를 리서치하게 되었다. 그 동안 D3.js는 막연하게 차트를 예쁘게 만들어주는 라이브러리라고만 알고있었다(더불어 입문자의 두개골을 많이 쪼개왔다는 것도). 개발자의 본업삽질에 충실하기 위해 아무생각없이 접한 D3.js는 역시나 만만한 라이브러리가 아니었다. 그래서 처음 접했을 때 어려웠던 부분, 간단한 부분인데 어이없이 삽질했던 부분등을 공유하여 다른분들의 삽질을 줄여주고 싶다.

1. D3.js

D3.js 문서의 첫 번째 장을 보면 D3.js is a JavaScript library for manipulating documents based on data.라고 쓰여있다. 이 줄을 읽자마자 그동안의 오해가 모두 사라졌는데, D3.js는 차트를 만들어 주는 라이브러리가 아니라는 것이다. D3.js는 data를 dom에 binding해주고 그것을 기반으로 HTML, svg, css를 조작할 수 있게 도와준다(차트와는 다르다 차트와는).

그렇기 때문에, 단순히 D3.js에 데이터를 바인딩해주는 정도로는 아무 일도 일어나지 않는다. DOM object들을 이용해서 데이터에 맞는 그림을 직접 그려주어야 한다.

2. D3 걸음마

먼저 svg를 만드는 간단한 예제를 보면 몇 가지 원리를 이해할 수 있다.

<!-- before -->
<body></body>

<script>
  d3.select('body').append('svg').attr('width', 800).attr('height', 300)
</script>

<!-- after -->
<body>
  <svg width="800" height="300"></svg>
</body>
  • selector
    body를 선택
  • append
    body 밑에 svg를 추가
  • attr
    svg에 각각의 attribute를 추가

간단하다!

3. selection

D3의 기본은 selection이라는 개념에서 시작한다. 말 그대로 DOM의 요소를 선택하는 것으로, 우리가 익히 아는 방법으로 수월하게 사용할 수 있다. CSS 선택자를 사용하는 방법이 그것이다.

d3.select('div')
d3.select('div.test')
d3.select('div.test:first-child')

위의 코드 모두 잘 동작하는 코드이다. 다만 select함수는 선택하려는 대상이 여러개라도 단 하나의 element를 return한다. 만약 test라는 class가 선언되어있는 div 태그를 모두 선택하려면 selectAll을 사용하면 된다. selectAll을 사용하게되면, 선택된 모든 요소를 배열로 반환한다.

d3.selectAll('div.test')

selector를 이용해 DOM을 선택하고 나면, 보통은 여러가지 modifier들을 이용한다. 보통의 경우 modifier들은 한 개 혹은 두 개의 parameter를 받으며, 하나의 parameter만 받는 경우 getter, 두 개 다 받게되면 setter 처럼 동작하게 된다(반드시 그런 것은 아니지만 그런 느낌으로 받아들이면 된다).

d3.select('svg').selectAll('rect').attr('width', 3).classed('test', true).style('font-size', '10px')

이 경우, 모든 선택된 rect에 대해 width, class, style이 적용된다.

두 번째 parameter에 함수를 넣어 이용하는 방법도 있다. 예를 들면, 특정 rect에 attribute를 추가하고 싶을때 사용하는 것이다.

d3.select('svg')
  .selectAll('rect')
  .attr('width', 3)
  .classed('test', true)
  .style('font-size', '10px')
  .attr('fill', function (d) {
    return d3.select(this).classed('test') ? 'blue' : null
  })

4. Data binding

DOM을 selection했다면 적당한 데이터를 binding해주어야 한다. 어찌보면 D3.js의 핵심 컨셉이며, 어떻게 데이터를 binding하느냐에 따라 그릴 수 있는 DOM의 모습이 달라지기때문에 중요한 부분이라고 할 수 있다. 기본적으로 data를 binding하는 방법은 예상한 대로 간단한다.

<body>
  <svg>
    <rect></rect>
    <rect></rect>
    <rect></rect>
  </svg>
</body>

<script>
  const data = [1, 2, 3]
  const rects = d3.selectAll('rect').data(data)
</script>

이렇게 하면, 각 rect element에 data 배열에 들어있는 요소들이 차례대로 binding된다. Binding된 데이터를 확인하려면, 간단히 rects를 출력해 보면 된다.

console.log(rects)
/** [Array(5)]
    0: Array(5)
      0: rect
      ...
      __data__: 1 // binded data
      __proto__: SVGRectElement
*/

이렇게 DOM에 binding된 데이터들은 나중에 svg attribute를 통한 시각화를 하는데에 중요하게 쓰이게 된다.

그런데 여기서 두가지 의문이 생긴다. Binding하는 데이터의 수만큼, 그에 해당하는 rect를 미리 html에 선언해 두어야 하는가? 라는 물음이 첫번째 이다. 만약 그렇다면 600개의 데이터를 binding해야한다고 가정할 때, 우리는 html 파일에 600개의 rect를 미리 배치해 두어야 한다.

두 번째는 binding되는 데이터가 동적으로 변할 때 이다. 600개의 rect가 미리 배치되어 있고, 590개의 데이터가 binding될 때는 조금 상황이 나을수 도 있지만, 만약 20개의 데이터가 동적으로 추가된다면? 상상만해도 끔찍한 일이 벌어진다는것은 굳이 시도해 보지 않아도 충분히 상상할 수 있다.

다음 글에서 이런 일들이 일어나지 않게 마법처럼 도와주는 enter함수에 대해 알아보고, 이를 이용해 간단한 bar chart를 만들어 보는 예제를 소개하겠다.


참고


© 2021, Built with Gatsby