Data Visualization and D3

Contents

  • Visualization Fundamentals

    • What is data visualization?
    • Good data visualization
    • Three steps to become a visualization/infographics designer
    • The data science process
    • The visualization spectrum
  • D3 Building Blocks

    • Recreating China's red dot
    • D3 scales
    • How scales work
    • Adding svg elements
    • Drawing circles
    • Make a bar chart
  • Problem Set: Visualization Fundamentals
  • Design principles
  • Dimple.js
  • Problem set: chart types and axes
  • Narrative structures
  • Animation and interaction
  • Data visualization reference

Visualization Fundamentals

What is data visualization?

Data Visualization (데이터 시각화)란 무엇을 의미할까요? 배우기 전에 스스로 한 번 생각해 보는 시간을 가져보도록 합시다. Edward Tufte의 책 The visual display of quantitative information에 따르면, 데이터를 색, 크키, 모양 등을 이용해 정보를 전달하는 것이라고 합니다. 개인적으로는 어떠한 분야에 의미 있는 데이터 또는 이야기를 도출해 그것을 시각화하고, 이해하기 쉽고 심미적으로도 좋게 정보를 전달하는 것이라고도 생각합니다.

Good data visualization

훌륭한 데이터 시각화란 무엇일까요? 이것은 데이터 시각화가 필요한 목적에 따라 다를 수 있습니다. 데이터 시각화를 하는 방법을 탐구(exploratory)를 위한 방법과, 설명(explanatory)을 위한 두 가지 방법으로 구분해 이야기해 보도록 하겠습니다.

탐구(exploratory)를 위한 시각화는 이 데이터가 무엇이고 어떠한 의미를 지니는지를 찾는 과정입니다. 많은 데이터에서 의미 있거나 흥미 있는 부분을 발견하면, 설명(explanatory) 단계로 넘어갈 수 있습니다. 탐구 단계에서 효율적인 데이터 시각화는, 보는 사람이 객관적이고 흥미도 있으며 차별화된 관점으로 데이터를 볼 수 있도록 하는 것입니다.

다음으로 설명(explanatory)을 위한 시각화가 갖추어야 할 사항으로 아래와 같이 5단계의 항목으로 정리할 수 있습니다.

  • 맥락(Context)을 온전히 이해하고, 독자가 누구이며 원하는 정보가 무엇인지 파악하기
  • 적절한 시각화 방법 설정하기
  • 의미 있는 정보를 제공하지 않는 불필요한 사항들 제거하기
  • 색, 사이즈, 위치를 이용해 원하는 곳에 독자의 관심 끌기
  • 스토리의 중요 부분을 시각화하기

Three steps to become a visualization/infographics designer

Alberto Cairo의 "visualization / infographics designer가 되기 위한 세 단계"라는 자료가 있는데, 직관적인 의미 전달을 위해 데이터를 시각화하는 과정을 단계적으로 설명한 자료입니다. 한 번 참고하면 좋을 것 같습니다. 이 자료에서 말하는 주요 세 단계는 아래와 같습니다.

  • Graphic은 이해를 위한 도구임을 인지하기
  • 자신이 관심을 가지는 토픽에 궁금증을 가지고 단순한 시각화 자료 만들기
  • 시각화 자료를 이해하기 쉽도록 계층화하고 서술화 하는 방법 학습하기

위 링크에서 Alberto Cairo는 교육 수준과 비만도에 대한 상관관계를 효율적으로 시각화하는 방법에 대해 단계적으로 설명하고 있습니다. 아래의 그래프를 참고하면, 왼쪽에는 고학력 인구의 비율을, 오른쪽은 비만율을 각 주별로 나타냈습니다. 특별한 설명이 없어도 직관적으로 고학력 비율과 비만율의 상관관계를 파악할 수 있습니다.

Data Visualization in Data Science

이 포스팅을 통해서 Data Visualization 이론을 D3를 이용해 데이터 시각화하는 방법에 대해 알아보고자 합니다. 하지만, Data Visualization도 다른 여러 작업과 조화를 이뤄야 하는 작업이기 때문에 이와 연관이 있는 작업 및 프로세스에 대해 먼저 확인해 보도록 하겠습니다.

아래 그림은 Ben Fry의 Computational Information Design에서 나온 것입니다. 이 그림은 acquiring, transforming 등 Data Visualization까지 이르는 여러 절차에 대해서 보여줍니다.

스크린샷 2019-07-02 오전 1 26 19

그림의 맨 왼쪽에는 일반적으로 Data Science Process의 시작으로 생각하는 acquire, parse 단계가 있습니다. 그리고 맨 오른쪽에는 다른 여러 작업을 거친 후 최종 product로 볼 수 있는 상호작용이 있는 데이터 시각화 단계가 있습니다.

D3 Building Blocks

데이터 시각화에 대해 기본적인 개념 등을 알아봤으며, 이번 섹션에서 Bubble Chart와 Bar Chart를 만들며, D3의 기본적인 기능을 알아보겠습니다.

Recreating China's Red Dot

아래의 사진은 의사이며 유명한 통계학자인 Hans Rosling의 데이터 시각화 자료입니다. 아래 차트와 차트 내 가장 큰 Circle인 중국을 D3로 만들어 보며, D3의 기본적인 개념을 익히도록 하겠습니다.

먼저 데이터를 살펴보면 2015년도 중국의 기대수명은 77년, GDP는 13,300이며 인구는 약 14억이었습니다.

어떠한 데이터이던 시각화를 위해서, 데이터의 값을 픽셀값으로 어떠한 방식으로 변경할지에 대해 생각해 봐야 합니다. 아래의 경우 개인당 수입을 x 축으로, 기대수명을 y 축으로 정했습니다. 그리고 x, y 값이 중국을 나타내는 원의 센터가 위치할 좌표입니다.

D3는 이러한 작업을 쉽게 하기 위해서 만들어졌으며, 데이터의 값을 픽셀값으로 변경하는 유용한 메소드가 많습니다.

D3 Scales

Scales는 데이터 시각화에서 핵심점인 데이터를 시각화 가능한 수치로 옮겨주는 중요하고 기본적인 개념입니다. 어떠한 수치화된 데이터를 포지셔닝 가능한 값으로 변경할 때 많이 사용합니다. scatterplot에서 dot 표기를 위해 미터로된 측정값을 픽셀값으로 매핑하는 작업을 예로 들 수 있습니다. 이 외에도, 컬러 테이블, stroke-width, font-size 등 어떠한 데이터의 비쥬얼 인코딩 작업에도 scale을 이용할 수 있습니다.

위 그래프에서 각 국가에 해당하는 원을 생성하기 위해서, 해당하는 데이터를 맵핑 후 픽셀값으로 변경해야 합니다. GDP 13,300은 x 픽셀로, 기대수명 77은 y 픽셀로 변경하고자 합니다. D3의 scale을 이용하기 위해서 입력 가능한 input 값과 맵핑 후의 output 값을 지정해 주어야 합니다. 예로, 아래 차트의 x축 scale은 250에서 100,000입니다.

How Scales Work

Scale에 대해 알아보기에 앞서, svg의 y축을 먼저 살펴보도록 하겠습니다. 일반적으로 그래프를 생각하면 y축은 아래에서 위로 갈수록 그 값이 커집니다. 하지만, svg에서는 그 반대입니다. svg에서 y축은 위에서 아래로 내려갈수록 그 값이 커집니다. 우리가 지금 만들고자 하는 그래프의 y축은 기대수명을 나타냅니다. 중국의 경우 기대수명이 높은데, 이것을 svg에서 그래프로 나타내기 위해서는 높은 기대수명의 값을 낮은 픽셀값으로 맵핑해야 합니다.

그래프의 y축 데이터 값인 15에서 90을 픽셀값 250에서 0으로 맵핑 해야합니다. 아래 그림의 초록색 축은 기대수명이고, 오른쪽의 하늘색 축은 맵핑 후의 픽셀값입니다. 가장 높은 기대수명인 90은 0픽셀로, 기대수명 15는 250픽셀로 변경이 되어야 합니다.

이를 위해서 우리는 input data를 픽셀 값으로 변경해주는 함수가 필요할 것입니다. D3의 domain 메소드를 이용해 input값을 참조하고, range 메소드를 이용해 output 값을 참조할 수 있습니다. domain method를 이용해 기대수명의 범위인 15 ~ 90을 지정해주고, range 메소드에는 기대수명에 상응하는 원하는 범위의 픽셀 값을 지정해줍니다.

Adding SVG Elements

이제 실제 코드로 Hans Rosling의 그래프를 만들어 보도록 하겠습니다. index.html 파일 하나를 만들고 d3 사용을 위해 sciprt를 추가해 주었습니다.

<!-- index.html -->
<body>
  <div class="root"></div>
</body>
<script src="https://d3js.org/d3.v5.min.js"></script>

데이터 시각화를 위한 원이나 사각형 등 svg 요소를 추가하기 위해서, 먼저 상위에 도면 영역으로 활용할 svg 요소를 추가해야 합니다. D3를 이용해 root 요소에 접근한 후 svg 요소를 추가합니다. 그리고 추가로 width와 height를 설정해 주었습니다.

<!-- index.html -->
<script>
  const svg = d3.select('.root').append('svg');
  svg.attr('width', 600).attr('height', 300);
</script>

중국을 나타내는 원을 생성하기 위해서 앞서 언급한 바와 같이 데이터값을 픽셀 값으로 변경해 주는 작업을 해야 합니다. 그리고 우리는 scale을 이용할 것입니다. scale의 종류에는 linear scale뿐만 아니라 log scale, power scale 등 다양하게 있습니다. 만들고자 하는 버블 차트 작성을 위해서 linear scale을 사용해, 아래와 같이 y축 scale을 생성할 수 있습니다.

const y = d3
  .scaleLinear()
  .domain([15, 90])
  .range([250, 0]);

y(90); // => 250
y(15); // => 0
y(52.5); // 125

x축의 GDP는 log scale을 활용할 수 있습니다.

const x = d3
  .scaleLog()
  .domain([0, 100000])
  .range([0, 600]);

x(250); // => 0
x(100000); // => 600

Drawing circles

Gapminder의 그래프에서 각 원은 해당 국가의 인구를 나타냅니다. 따라서, 원의 크기는 중국의 인구수 13억에 상응해야 합니다. 넓이를 구하기 위해 반지름을 도출해야 하는데 아래와 같이 반지름은 square root 인구수에 상수를 곱한 값으로 정리할 수 있습니다.

위를 참고로 d3를 이용해 15 ~ 40픽셀 범위의 반지름을 도출하는 함수를 변수 r에 할당하도록 할 수 있습니다.

const r = d3
  .scaleSqrt()
  .domain([52070, 1380000000])
  .range([15, 40]);

r(52070); // => 15
r(1380000000); // => 50

여기까지 데이터를 기반으로 x, y, r scale을 만들었습니다. 우리가 작성한 scale을 기반으로 그래프상 중국의 좌표를 console에서 먼저 확인해 보도록 하겠습니다. 앞서 중국의 기대수명은 77년, GDP는 13,330, 인구수는 13.8억으로 확인했습니다. 이것은 좌표로 변환하면 아래와 같습니다.

y(77); // => 43.33333333333334
x(13330); // => 398.1976156961322
r(1380000000); // => 50

앞서 작성한 y scale은 범위가 15 ~ 90인 기대수명을 250 ~ 0픽셀로 맵핑합니다. svg에서 y축의 최상단이 0픽셀부터 시작하는 점을 고려했을 때 43.3333은 y축의 윗부분에 위치하기 때문에, 수치상으로 봤을 때 적절하다고 생각할 수 있습니다.

이제 처음에 캔버스로 사용하기 위해 만들었던 svg에 원을 추가할 수 있습니다. 현재까지 작성한 전체 코드와 이를 시각화한 UI는 아래와 같습니다.

const svg = d3.select('.root').append('svg');
svg
  .attr('width', 600)
  .attr('height', 300)
  .style('border', '1px solid #ddd');

const y = d3
  .scaleLinear()
  .domain([15, 90])
  .range([250, 0]);
const x = d3
  .scaleLog()
  .domain([250, 100000])
  .range([0, 600]);
const r = d3
  .scaleSqrt()
  .domain([52070, 1380000000])
  .range([15, 40]);

svg
  .append('circle')
  .attr('fill', 'red')
  .attr('r', r(1380000000))
  .attr('cx', x(13330))
  .attr('cy', y(77));

Make a bar chart

다음으로, Mike Bostock의 Let's Make a Bar Chart 튜토리얼을 참고로 bar chart를 만들며 기본적인 개념을 알아보도록 하겠습니다. 참고로 Mike Bostock은 D3를 처음 만든 컴퓨터 과학자입니다. D3를 이용해 bar chart를 만들기 전 html과 svg tag를 이용해 직접 bar chart를 아래와 같이 만들 수 있습니다.

<!-- index.html -->
<style>
  .chart rect {
    fill: steelblue;
  }

  .chart text {
    fill: white;
    font: 10px sans-serif;
    text-anchor: end;
  }
</style>
<body>
  <svg class="chart" width="420" height="120">
    <g transform="translate(0,0)">
      <rect width="40" height="19"></rect>
      <text x="37" y="9.5" dy=".35em">4</text>
    </g>
    <g transform="translate(0,20)">
      <rect width="80" height="19"></rect>
      <text x="77" y="9.5" dy=".35em">8</text>
    </g>
    <g transform="translate(0,40)">
      <rect width="150" height="19"></rect>
      <text x="147" y="9.5" dy=".35em">15</text>
    </g>
    <g transform="translate(0,60)">
      <rect width="160" height="19"></rect>
      <text x="157" y="9.5" dy=".35em">16</text>
    </g>
    <g transform="translate(0,80)">
      <rect width="230" height="19"></rect>
      <text x="227" y="9.5" dy=".35em">23</text>
    </g>
    <g transform="translate(0,100)">
      <rect width="420" height="19"></rect>
      <text x="417" y="9.5" dy=".35em">42</text>
    </g>
  </svg>
</body>

css style은 svg element에 일반적인 div element와 동일하게 적용됩니다. 하지만, svg element는 부모 element에 기반해 position을 absolute 하게 지정해야 합니다. 또 한 가지 알아야 할 점은 text의 경우 text element를 이용해햐 하며, text element의 경우 padding과 margin이 없기 때문에 적절하게 offset 값을 할당해 위치를 지정해야 합니다.

우리는 D3를 이용해 위의 bar chart를 좀 더 다이나믹하게 만들 수 있습니다. 데이터를 전달받아 bar chart를 생성하는 draw 함수를 아래와 같이 작성할 수 있습니다.

const data = [4, 8, 15, 16, 23, 42];
const width = 420;
const barHeight = 20;

const x = d3
  .scaleLinear()
  .domain([0, d3.max(data)])
  .range([0, width]);

const chart = d3
  .select('.chart')
  .attr('width', width)
  .attr('height', barHeight * data.length);

const bar = chart
  .selectAll('g')
  .data(data)
  .enter()
  .append('g')
  .attr('transform', (d, i) => `translate(0, ${i * barHeight})`);

bar
  .append('rect')
  .attr('width', x)
  .attr('height', barHeight - 1);

bar
  .append('text')
  .attr('x', d => x(d) - 3)
  .attr('y', barHeight / 2)
  .attr('dy', '.35em')
  .text(d => d);

D3를 이용해 같은 bar chart를 만들었습니다. 코드를 살펴보면, 상위에 전체 svg의 width와 bar의 높이를 설정했습니다. 이러한 방식으로 코드 작성 시, 전체 그래프의 height는 data의 length에 따라 달라집니다. 각 bar는 rect, text element를 자식 요소로 가지고 있는 g tag로 이루어집니다.

위 코드 중 아래의 코드는 제가 D3를 처음 접했을 때 이해하기 어려운 부분이었습니다.

const bar = chart
  .selectAll('g')
  .data(data)
  .enter()
  .append('g')
  .attr('transform', (d, i) => `translate(0, ${i * barHeight}`);

selectAll 메소드는 chart svg 하위의 모든 group 태그를 선택합니다. data 메소드는 데이터로 전달받은 배열을 선택한 요소에 바인딩 하고, 데이터가 바인딩 된 새로운 selection을 반환합니다. enter 메소드는 바인딩 된 데이터가 있지만 이에 상응하는 DOM 요소가 없는 노드를 반환합니다. 다음으로, append 메소드는 enter 메소드가 반환한 요소를 chart svg의 자식 요소로 추가하는 역할을 합니다. data, enter에 대해서 간단히 확인해 봤으며 뒤쪽에서 좀 더 상세히 다시 확인해 보도록 하겠습니다.

Data Visualization Reference