[D3.js] Hierarchical Bar Chart 초심자가 분석하기

jiho·2021년 11월 13일
0

d3js

목록 보기
2/4

(작성 중...)

D3.js 라이브러리 활용능력을 키우고 싶어서 학습 중 입니다. 대부분의 d3.js 시각화 작품들이 공개되어있지만 코드 자체만으로 분석하기에는 d3.js API구성을 잘 알아야합니다. 이번 포스트에서는 D3.js로 구성된 Hierarchical Bar 차트를 분석해보고 최종 목표는 React Ref를 통해 React.js에서 적용해보는 것까지 연습해보겠습니다.

https://observablehq.com/d/bae2e812be6ce83a

ObservableQ 서비스를 이용해서 코드를 학습을 진행했습니다.

Hierarchical bar Chart란?

일반 바 차트는 정적인 데이터만을 볼 수 있지만 계층적으로 내부의 데이터를 다른 세부적으로 시각화 하고 싶을 경우에 계층적 바차트는 더 훌륭한 인사이트를 줄 수 있는 시각화 차트입니다.

개발 환경

레퍼런스 코드는 ObservableQ에 있었으나 저는 VsCode 옮겨와서 테스트 해보겠습니다.

개발 환경 : VSCode + d3.jsv6 CDN + http-server

작성했던 코드는 해당 Repository 에서 확인할 수 있습니다.

레퍼런스 코드 분석 🧐

D3.js의 API 구성을 모른다면 다소 이해하기가 어려운 코드들이 많아 보입니다. 저도 관련 자료나 문서를 많이 읽어보면서 익숙해지는 과정이라 생각하고 최대한 각 API에 대한 설명도 구체적으로 남겨보겠습니다.

빠른 이해를 위해서 Top-Bottom 🧐 방식으로 코드 분석을 진행해보겠습니다.

시작 코드

우선 가장 최상단의 코드, 가장 추상화가 되어 있는 코드 영역인 최상위 SVG DOM을 나타내는 코드를 살펴보겠습니다.

<body>
    <svg class="root"></svg>
</body>
import * as d3 from "https://cdn.skypack.dev/d3@6";
const svg = d3.select(".root")
      .attr("width", width)
      .attr("height", height);

d3는 DOM selector할 때 위와 같이 d3.select("[selector]") 이렇게 DOM 노드를 선택해서 D3 Selection으로 객체로 가져오게 됩니다. (그 외 다양한 select API들이 많이 존재합니다. d3.selection 모듈을 확인해보세요)

그리고 주로 attr를 메소드 체이닝 방식으로 정의해서 DOM에 즉각적으로 반영합니다.

const ROOT = document.querySelector(".root");
ROOT.setAttribute("width", width + "px");
ROOT.setAttribute("width", height + "px");

표준 DOM API로는 위와 같이 구성할 수 있습니다.

데이터 구조

데이터 시각화에서 데이터 구조만큼 중요한 것이 없습니다. D3에서 제공해주는 d3.hierarchy함수를 통해 계층적인 데이터 전처리해서 구성해보겠습니다.

우선 raw한 데이터의 형태(json)는 아래와 같습니다.

{
 "name": "대한민국",
 "children": [
  {
   "name": "경기도",
   "children": [
      {
        "name": "용인시",
        "value": 200000
      },
      {
        "name": "수원시",
        "value": 300000
      }
	]
  }
}
const response = await fetch("https://raw.githubusercontent.com/peanut-lover/d3-tool-box/master/data/flare-2.json")
const data = await  response.json();
const root = d3.hierarchy(data)
  .sum(d => d.value)
  .sort((a, b) => b.value - a.value)
  .eachAfter(d => d.index = d.parent ? d.parent.index = d.parent.index + 1 || 0 : 0)

d3.hierarchy() 생성 함수는 계층적 구조의 root node를 받고 그것에 몇가지 property와 함수를 추가해서 변형합니다. 그리고 결과로 나온 데이터는 tree,treemap,parition 같은 시각화를 만들어주기 위해 사용될 수 있습니다.

default 로 children이라는 리스트에 자식 노드를 담는 데이터를 받을 수 있습니다. 만약 children이 이라는 이름이 아닌 다른 이름으로 자식노드 리스트를 담고 있다하더라도 d3.hierarchy(data, d => d.[다른이름])과 같이 처리할 수 있습니다.

sum(d => d.value) : 각 노드마다 자식도드의 value의 합을 property로 추가
sort((a, b) => b.value - a.value) : 자식 노드 간에 내림차순으로 정렬
eachAfter : 후순위순회를 하면서 매번 호출 합니다. 각 노드가 몇번째 자식인지나타내는 index property를 추가하기 위한 코드입니다.

해당 코드만으로 설명할 것이 굉장히 많지만 유용한 형태로 데이터를 전처리한다고 요약하고 다음 내용으로 넘어가겠습니다. (시간이 남으면 구체적으로 설명)

interpolate를 위한 Scale 함수 정의

일반적으로 그래프를 그릴 때, 화면의 크기와 데이터 도메인의 범위를 보간(interploate)시켜주는 작업이 있습니다.
d3에서는 아주 다양하고 유용한 scale 함수 생성기들을 가지고 있습니다.

여기서 사용할 scale 함수는 d3.scaleLinear 입니다.

const x = d3.scaleLinear().range([margin.left, width - margin.right]);

// 애니메이션을 위해 domain을 따로 정의할 수 있도록 구성
x.domain([0, maxValue])

위와 같이 데이터의 크기를 화면에서의 크기로 선형적으로 변환할 수 있는함수를 이용하면 barchart를 그릴 수 있을 것입니다.

X 좌표계 그리기

그래프에서 X축과 Y축을 구현하는 것을 생각보다 까다롭습니다. 하지만 이전에 정의했던 scale 함수와 d3.axisTop 함수를 이용하면 보다 쉽게 지표를 나타내는 x축 좌표계를 만들 수 있습니다.

const xAxis = g => g.call(d3.axisTop(x).ticks(10, "s"))

해당 함수는 g라는 svg selection을 argument로 받았을 때 해당 <g></g>내부에 top 좌표계를 10개의 tick으로 구성해서 아래와 같이 그려줍니다.

Bar를 그리는 bar(svg, down, d, selector) 구현

작성 중...

하위 데이터로 이동하는 down(svg, d) 함수 정의

작성 중...

상위 데이터로 이동하는 up(svg, d) 함수 정의

작성 중...

요약

가독성 높여서 정리하기 쉽지가 않았습니다. 최대한 D3.js를 처음보는 사람도 이해할 수 있도록 다시 정리해봐야겠습니다.

profile
Scratch, Under the hood, Initial version analysis

0개의 댓글