본문 바로가기

💻 내 소개 안녕하세요 엄청짱 프로그래머 손다빈 입니다.
  • 나이 : 96년생
  • 특이사항 : MZ세대, INFJ, 오른손잡이, 아이폰 유저
  • 좋아하는 음식 : 햄버거피자치킨솥뚜껑삼겹살떡볶이오튀김밥
  • 취미 : 개발, Programming, 코딩, 프로그래밍, Coding

🥷기술
Unity
Godot
Cpp
Javascript
D3
Vue

🐱 우리집 고양이 소개
츄르 먹은 후 츄르 먹기 전
  • 이름 : 콜라
  • 나이 : 8살
  • 종 : Nado moreum

📱 개인 프로젝트
🏢 참여한 프로젝트
빌런즈 Life is Pair 도씨어부키우기 직장상사혼내주기 서바이벌빙고 SlitherCoin

🌱 내 잔디밭

D3.js 쉽게 시작하세요 본문

글 묶음/사탄도 외면한 javascript

D3.js 쉽게 시작하세요

초긍정 개발자 다빈맨 2019. 3. 22. 23:37

| 데이터 시각화 라이브러리, D3.js






누군가에게 데이터를 보여줄 때 단순히 텍스트 덩어리를 보여주는게 아니라 차트나 그래프로 보여주는것이 아무래도 더 신사적인 방법이 아닐까 싶습니다. 저는 여기서 여러분들을 신사적으로 만들어줄 데이터 시각화 라이브러리인 D3를 빠르게 입문할 수 있는 가이드를 제공합니다. 이 글에서는 최소한의 이해와 필요한 정보만 빠르게 익히고 간단한 점 차트와, 선 그래프를 그려보도록 하겠습니다.


- D3 라이브러리 준비하기

- 무조건 알아둬야 하는 scale 함수

- 표출할 데이터 준비하기

- x축과 y축 만들기

- 점 차트 그리기

- 선 그래프 그리기


챕터를 구분해놓긴 했지만 순서대로 읽는다는 가정하에 작성하였습니다.




| D3 라이브러리 준비하기

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
</body>
</html>

https://d3js.org/ 링크에서 직접 라이브러리를 다운로드 받으실 수 있지만 이 글에서는 그냥 CDN 을 이용해서 라이브러리를 가져옵니다.

최소한의 html 문서의 형식에다가 D3.js 파일을 불러오는 script 태그만 추가하였습니다.




| 무조건 알아둬야 하는 scale 함수


본격적으로 들어가기 전에 d3 에서 제공하는 여러가지 함수 중에서 거의 무조건적으로 사용되는 scale 함수에 대해서 살펴봅니다.  scale 함수는 여러가지가 있지만 여기서는 scalelinear() 와 scaleTime() 만 예시로 보여줍니다. 먼저, scaleLinear() 함수를 볼까요?

<body>
<script>
const scaleFunc1 = d3.scaleLinear().domain([1, 5]).range([1, 10]);
console.log(scaleFunc1(3)); //5.5 출력
console.log(scaleFunc1(5)); //10 출력
</script>
</body>

예제를 보고 바로 이해가 되시면 좋겠지만 설명이 필요할겁니다. 일단 scale 함수의 결과는 또 다시 함수입니다. 즉, scale 함수는 함수를 만드는 함수 입니다. 또, 예제를 보면 알겠지만 항상 domain(), range() 함수와 같이 사용됩니다. 그렇다면 scale 함수는 어떤 함수를 만들어 줄까요? 간단하게 정리하면 domain() 범위의 입력값이 들어오면 range 범위의 결과값으로 바꿔주는 함수를 만들어줍니다!


이해하기 쉽게 그림으로 그려 보았습니다. 이제 다시 위의 예제를 살펴보면 scaleLinear() 함수로 만든 함수, scaleFunc1 을 이용해서 3 과 5 를 각각 함수에 넣으면 출력값이 왜 5.5와 10이 나오는지 이해되실겁니다.

const scaleFunc2 = d3.scaleTime()
.domain([new Date("2019-03-22T-01:00:00"), new Date("2019-03-22T-01:00:05")])
.range([1, 10])

console.log(scaleFunc2(new Date("2019-03-22T-01:00:00"))); //1 출력
console.log(scaleFunc2(new Date("2019-03-22T-01:00:05"))); //10 출력

scaleTime() 함수도 개념은 완전히 같습니다. 다른점이 있다면 범위를 숫자가 아닌 Date 값으로 지정할 수 있습니다. 

sclae 함수를 이용하면 이제 데이터의 값들을 화면상의 어느좌표상에 위치해야할지 결정할 때 사용할 수 있습니다.

예를들어 위 그림에서 데이터의 value 범위는 1~8 입니다. 이 범위에 해당하는 값들을 만약 200x200 사이즈의 화면상에 점으로 표출하고 싶다고 할 때 value 값을 표출하고자 하는 범위 상의 y 값이나 x 값으로 변환해야 합니다. 이때 scale 함수를 사용합니다!




| 표출할 데이터 준비하기


<body>
<svg width="350px" height="350px">
</svg>
<script>
const data = [
{ value : 3, time : new Date("2019-03-22T03:00:00") },
{ value : 1, time : new Date("2019-03-22T03:05:00") },
{ value : 9, time : new Date("2019-03-22T03:10:00") },
{ value : 6, time : new Date("2019-03-22T03:15:00") },
{ value : 2, time : new Date("2019-03-22T03:20:00") },
{ value : 6, time : new Date("2019-03-22T03:25:00") }
];
</script>
</body>

이제 위에서 실습했던 scale 함수의 내용은 script 태그에서 모두 지우고, 위와 같이 간단한 데이터를 하나 준비합니다. 그냥 복사해서 쓰세요.


또, d3는 기본적으로 svg 태그 안에다가 무언가를 그리기 때문에 svg 태그가 필요합니다. body 태그 안에 svg 태그를 추가해둡니다. svg 의 크기는 너비와 높이를 모두 350px로 하면 좋을 것 같네요. .




| x축과 y축 만들기



이렇게 생긴 x축과 y축을 만들어 볼겁니다.

    const xScale = d3.scaleTime()
.domain([new Date("2019-03-22T03:00:00"), new Date("2019-03-22T03:25:00")])
.range([20, 330]);
const yScale = d3.scaleLinear()
.domain([1, 9])
.range([330, 20]);

script 태그 안에 선언해놓은 data 배열 변수 아래에 코드를 이어서 작성하면 됩니다. 축을 만들 때, scale 들의 range 범위를 사용하기 때문에 위에서 배운 scale 함수를 사용해서 두개의 함수(xScale, yScale)를 만들어냅니다. domain 은 데이터의 일반적으로 최소, 최대값으로 설정하고 range 는 표출할 범위의 너비, 높이 픽셀값이 됩니다.

//SVG 안에 G 태그를 생성한다.
const xAxisSVG = d3.select("svg").append("g");
const yAxisSVG = d3.select("svg").append("g");

scale 함수를 이용해서 xScale과 yScale 을 만들어놨으니 이제 이걸 이용해서 축을 만들 수 있습니다. 축은 보통 g 태그에 생성하는데, 이 g 태그는 svg 태그 안에서 여러 요소들을 그룹화 하는데 사용하는 태그입니다. 쉽게 말해서 축에 사용되는 여러 막대기나 텍스트들의 집합을 묶어줄 g 태그가 필요한거죠. 따라서 위의 두 코드를 통해서 svg 태그안에 g 태그를 추가합니다. y축과 x축 각각 한개씩 만들어주면 되겠죠?


※ d3의 select 함수는 CSS 선택자 규칙을 따릅니다.

//축을 만드는 함수를 만든다.
const xAxis = d3.axisBottom(xScale).tickSize(10).ticks(10);
const yAxis = d3.axisRight(yScale).tickSize(10).ticks(10);
xAxis(xAxisSVG); //x축을 만드는 함수로 SVG > G 태그에 축을 생성한다.
yAxis(yAxisSVG); //y축을 만드는 함수로 SVG > G 태그에 축을 생성한다.

d3 에서는 완성된 형태의 축을 바로 생성할 수 있는 axis 함수를 제공합니다. 그런데 이 함수의 반환 값은 함수입니다. 즉, 위에서 사용한 scale 계열의 함수들과 동일하게 함수의 결과로 함수가 만들어지는 것이죠.  axis 함수들을 이용해서 축을 만드는 함수를 만들 수 있는거죠.


axis* 계열의 함수는 axisBottom, axisTop, axisRight, axisLeft 가 있는데, 기억하기 쉽게 axis 뒤에 막대기가 튀어나올 방향을 지정하면 된다고 생각하면 쉽습니다. 예를들어서 axisBottom 은 막대기가 아래로 향한다고 생각하면, 다음과 같은 축이 만들어 집니다.


tickSize는 축마다 달려있는 막대기들을 tick 이라고 하는데, 이 tick의 높이값을 설정합니다. 10이기 때문에 10픽셀 입니다. ticks 는 막대기들이 축에 분포될 양을 설정합니다. 10이라고 해서 10개가 분포될 것 같지만 그렇지 않고 함수 내부의 알고리즘에 의해 적절한 개수로 배치됩니다.


여기까지 작업한 후 body 태그 안의 전체 코드는 이렇습니다.

<body>
<svg width="350px" height="350px">
</svg>
<script>
const data = [
{ value : 3, time : new Date("2019-03-22T03:00:00") },
{ value : 1, time : new Date("2019-03-22T03:05:00") },
{ value : 9, time : new Date("2019-03-22T03:10:00") },
{ value : 6, time : new Date("2019-03-22T03:15:00") },
{ value : 2, time : new Date("2019-03-22T03:20:00") },
{ value : 6, time : new Date("2019-03-22T03:25:00") }
];

const xScale = d3.scaleTime()
.domain([new Date("2019-03-22T03:00:00"), new Date("2019-03-22T03:25:00")])
.range([20, 330]); // [0, 350] 을 넣어도 되지만.. 그러면 축이 너무 붙어있어서 20~330으로 설정.
const yScale = d3.scaleLinear()
.domain([1, 9])
.range([330, 20]); // SVG 좌표상에서 y값이 높을수록 아래로 향하기 때문에 뒤집어서 330~20으로 설정.

//SVG 안에 G 태그를 생성한다.
const xAxisSVG = d3.select("svg").append("g");
const yAxisSVG = d3.select("svg").append("g");
//축을 만드는 함수를 만든다.
const xAxis = d3.axisBottom(xScale).tickSize(10).ticks(10);
const yAxis = d3.axisRight(yScale).tickSize(10).ticks(10);
xAxis(xAxisSVG); //x축을 만드는 함수로 SVG > G 태그에 축을 생성한다.
yAxis(yAxisSVG); //y축을 만드는 함수로 SVG > G 태그에 축을 생성한다.
</script>
</body>

제가 xScale 과 yScale 의 range 부분을 좀 더 수정하였습니다. 이유는 주석으로 달아놓았으니 참고하세요! 수정하기 전과 결과를 비교해보셔도 좋습니다. 이 상태에서 브라우저로 소스파일을 열어보시면 아래와 같이 축이 만들어지는걸 확인하실 수 있습니다.


위 예제에서는 다루지 않았지만, 보통 domain 과 range 값은 데이터의 최소/최대 값으로 설정하는 경우가 많습니다. d3 에는 데이터의 최소/최대 값을 배열로 반환해주는 d3.extent 가 있으니 활용해보세요.



그런데 축이 위쪽에 위치해 있는걸 알 수 있습니다. 기본적으로 SVG 내부에서 좌표계는 y값이 높을수록 아래로 향하기 때문입니다. 축의 위치를 변경하면 되겠네요.

const xAxisSVG = d3.select("svg").append("g").attr("transform", "translate(0, 330)");

x 축 g 태그를 생성하는 부분에 attr 함수로 transform 속성을 변경할 수 있습니다. translate 를 이용해서 y 값의 위치를 330으로 수정합니다.



이렇게 하면 이쁘게 축이 완성되었습니다!




| 점 차트 그리기



축이 준비되었으니 이제 점 차트를 그려봅시다. 결과물을 미리 보자면 위와 같이 생겼네요. 데이터와 scale 함수를 만들어 놓은 상태라면 이제 점을 그리는건 비교적 짧은 코드로 쉽게 만들어낼 수 있습니다.

//점을 생성한다.
d3.select("svg").selectAll("circle") // 1.SVG 태그 안에 있는 circle을 모두 찾는다.
.data(data) // 2.찾은 요소에 데이터를 씌운다.
.enter() // 3.찾은 요소에 개수보다 데이터가 더 많을경우..
.append("circle") // 4.circle 을 추가한다.
.attr("r", 5) // - 반지름 5픽셀
.attr("cx", d=>xScale(d.time)) // - x 위치값 설정.
.attr("cy", d=>yScale(d.value)) // - y 위치값 설정.
.style("fill", "black") // - 검정색

다만 이 짧은 코드가 많은 내용을 담고 있다는게 문제입니다. D3에서 데이터개수만큼 요소를 생성해야하는 경우에는 다음과 같은 방식으로 요소를 생성합니다.

1. 요소를 선택하고, 데이터를 씌웁니다. 위 그림에선 요소가 2개가 선택되었고 데이터가 3개가 씌어진 경우입니다.



2. 데이터 하나당 요소 하나씩 짝지어 줍니다. 이 다음이 중요한데, 이제 데이터가 더 많거나 요소가 더 많은 경우가 분명히 생기겠죠?



3. D3 에서는 이런식으로 데이터가 더 많거나 요소가 더 많은 경우 특정한 행동을 하도록 지시할 수 있는데, 이 때 사용하는 함수가 enter() 와 exit() 입니다. enter() 는 데이터가 더 많은경우, exit()은 요소가 더 많은 경우 사용됩니다. 따라서 지시할 행동이 반드시 enter() 와 exit() 함수 뒤에 바로 등장해야합니다.

d3.select("svg").selectAll("circle")
.data(data)
.enter()
.append("circle")

그래서 enter() 뒤에 요소보다 데이터가 더 많을경우 지시할 함수가 바로 붙습니다. 여기서는 append가 붙었기 때문에 circle 요소를 추가하게 됩니다. 위 코드를 그림으로 보면 다음과 같습니다.



circle 요소를 선택할 때, 하나도 없을테니 요소는 0개 입니다. 그리고 편의상 그림에는 데이터를 3개만 표시했지만 위 코드상으론 배열의 크기가 6개라서 실제로는 6개의 데이터가 씌어지겠죠. 이 상태에서 enter()~append() 조합으로 부족한 circle 요소만큼 생성되면서 요소 각각마다 데이터가 1:1 로 연결됩니다.


D3는 요소의 속성을 부여할 때 연결된 데이터 정보를 사용할 수 있습니다.

.attr("r", 5) // - 반지름 5픽셀
.attr("cx", d=>xScale(d.time)) // - x 위치값 설정. scale 함수를 이용해서 정확한 x 위치를 얻을 수 있다.
.attr("cy", d=>yScale(d.value)) // - y 위치값 설정.
.style("fill", "black") // - 검정색

여기서 요소에 연결된 데이터를 사용하는 부분은 'cx' 속성과 'cy' 속성입니다. attr 함수의 두번째 인자에는 함수가 올 수 있으며 함수를 인자로 전달하는 경우에 반드시 첫번째 매개변수로 요소에 연결된 데이터값이 넘어옵니다.  


- xScale 함수를 사용해서 각 요소마다 연결된 데이터가 가진 time 값을 입력값으로 넣으면 좌표상의 x 포지션을 결과값으로 줍니다.

- yScale 함수를 사용해서 각 요소마다 연결된 데이터가 가진 value값을 입력값으로 넣으면 좌표상의 y 포지션을 결과값으로 줍니다.




| 선 그래프 그리기



이제 마지막으로 선 그래프를 그려보도록 하겠습니다. 선 같은 경우는 path 요소를 이용해서 생성합니다. 이번에도 코드를 먼저 보여드리도록 하겠습니다.


//선을 생성하는 함수
const linearGenerator = d3.line()
.x(d=>xScale(d.time))
.y(d=>yScale(d.value))

d3.select("svg")
.append("path") // SVG 태그 안에 path 속성을 추가한다.
.attr("d", linearGenerator(data)) // - 라인 생성기로 'd' 속성에 들어갈 좌표정보를 얻는다.
.attr("fill", "none") // - 라인 안쪽 채우지 않음.
.attr("stroke-width", 2) // - 굵기
.attr("stroke", "black") // - 검정색


선을 그릴때 path 요소에 있는 'd' 속성값을 참조해서 그려집니다. 'd' 속성 안에 모든 라인에 대한 정보가 다 들어가는거죠.



브라우저의 개발자 모드에서 만들어진 path 요소의 d 속성을 보시면 괴상한 숫자와 영어로 조합된 텍스트가 들어가 있습니다. 이 숫자와 영어가 라인의 꼭짓점의 좌표 하나하나를 의미하며, 알파벳은 라인의 끝을 매꿀것인지 매꾸지 않을것인지 등에 대한 내용이 들어갑니다.

//선을 생성하는 함수
const linearGenerator = d3.line()
.x(d=>xScale(d.time))
.y(d=>yScale(d.value))

그래서 다른부분은 크게 이해하실 부분이 없고 이 line() 함수만 살펴보면 됩니다. line() 함수는 들어온 데이터 값을 완성된 'd' 속성의 값으로 반환해주는 함수를 만들어줍니다. 마찬가지로 선 꼭짓점의 x 와 y 포지션값을 지정할 수 있는 x() 함수와 y() 함수를 같이 구성해야 합니다. 어렵지 않죠?




| D3.js 더 배워보세요!


D3는 훨씬 사용할 수 있는 부분이 많은 라이브러리입니다. 지도를 표출하거나 물리가 작용하는 시각화도 가능하며 사용자와 상호작용이 가능한 형태의 차트도 만들 수 있습니다. 애니메이션도 넣을 수 있고요. 다양한 D3 예제를 확인하고 싶다면 아래 링크에 접속해보세요.

https://github.com/d3/d3/wiki/Gallery


마지막으로 이 글에서 사용한 예제파일을 첨부하였습니다.


d3_example.html