데이터 가시화 (d3.js , Plotly, Grafana, Kibana 등)

밑바닥 부터 시작하는 d3.js (version 4) 와 Typescript 를 이용한 데이터 가시화

[하마] 이승현 (wowlsh93@gmail.com) 2018. 2. 19. 11:42


https://bl.ocks.org 싸이트를 보면 굉장히 다양한 d3.js 예제들이 있으니 참고 하십시요.
이 글은 글 마지막의 레퍼런스를 요약한 것이니, 구체적인 설명은 링크를 따라가서 읽어보십시요.
아래 내용은 윈도우8에서 테스팅하고 확인 하였습니다. (패키지 버전을 확인하세요. 버전이 다르면 본 문서의 명령어가 안먹힐 수도 있습니다.)

+  


개발환경 세팅 

자바스크립트 개발 환경 세팅

Node 설치  (npm 이용 및 브라우저 없이 실행하기 위함)

VS CODE 편집기 설치  (편집기는 아무거나~) 


1. 프로젝트 폴더 생성

mkdir d3v4-with-ts
cd d3v4-with-ts


2. 폴더 내에서 package.json 생성 (패키지 및 프로젝트 관리를 위함) 
npm init -y

3. 패키지 받아오기 (d3 와 타입스크립트를 설치합니다. -S 는 --save , -D 는 --save-dev) 
npm i -S d3@^4.13.0
npm i -D typescript@^2.4.2
npm i -D @types/d3@^4.10.0


타입스크립트 컴파일 환경 세팅
(타입스크립트를 Node 실행기가 이해하는 언어로 변경하기 위함)

 

4. 타입스크립트 컴파일러 설정 파일 만들기 (파일명: tsconfig.json)

{
"compilerOptions": {
"noImplicitAny": true,
"target": "es5",
"sourceMap": true,
"declaration": true,
"module": "es2015",
"moduleResolution": "node"
},
"exclude": [
"node_modules",
"dist"
]
}


  •  noimplicitAny : 모든것은 타입을 명시적으로 가져야 한다. 
  •  es5 버전의 자바스크립트로 컴파일하라. 
  •  소스캡을 만들어라. 브라우저에서 오리지날 파일에 대한 디버깅을 할 수 있다.
  •  es2015 (or es6) 모듈 타입을 사용하라. 
  •  노드가 하는 것처럼 모듈을 해석하라. 더 자세한 것은 여기 참고 here.




5. src 폴더를 만들고 그 안에 app.ts 파일을 만들고 일단 아래 내용 입력 

console.log('hello, world');


6. 커맨드라인에서 아래를 입력하여 타입스크립트를 js 로 변경 해보자

.\node_modules\.bin\tsc

build01

이렇게 js 파일들이 생겨 날 것이다. 

7. 위의 명령이 너무 길기 때문에 줄여 보자. package.json 의 script 섹션안에 아래 내용 입력

"build"
"tsc"

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc"
},


파일을 저장하고 이제 다음과 같이 명령하면 자동적으로 타입스크립트를 빌드 할 것이다.

npm run build


현재는 src 폴더 안에 ts 파일과 컴파일된 js 파일이 같이 저장될 것이나, 나중에는 따로 저장하게 만들어서 관리하기 편하게 할 것이다.

롤업을 이용한 번들링  
(브라우저가 이해하는 형태로 변경하기 위함)

8. 이제부터 번들링을 시작해보자. rollup 패키지를 설치한다. (참고로 라이브러리 번들링에는 Webpack 보다 더 낫다고 한다. Webpack and Rollup: the same but different)

npm i -D rollup@^0.45.2

9. 모드 모듈을 해결하기 위해서 플러그인도 설치 하자.
npm i -D rollup-plugin-node-resolve@^3.0.0

10. rollup 을 설정하기 위해서 rollup.config.js 파일을 만들고 아래 내용 입력

import resolve from 'rollup-plugin-node-resolve';

export default {
entry: 'src/app.js',
dest: 'src/bundle.js',
format: 'umd',
plugins: [
resolve({
jsnext: true,
main: true,
module: true
})
],
moduleName: 'app'
};



11. 아래 명령어를 실행해서 번들링하자. src/bundle.js 파일이 생겨 날 것이다.
.\node_modules\.bin\rollup -c
 (-c 는 설정파일을 이용하라는 의미)

12. 역시 너무 길기 때문에 다시 package.js 의 scripts 섹션에 build 를 수정하자.

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc && rollup -c"
},


파일을 저장하고 이제 다음과 같이 명령하면 자동적으로 타입스크립트를 컴파일해서 js 만들고 js 를 번들링 해서 최종적으로 브라우저가 이해 할 수 있는 js 파일로 만들 것이다. (파일 크기를 줄이고, 난독화하는 설정도 추가 가능하다) 


npm run build

비스 시작하기 

13.  간단하게 파일을 서비스 하기 위해 live-server 설치
npm i -D live-server@^1.2.0

14.  package.json 의 scripts 섹션에 아래 추가 (html,js,css 를 읽어서 서비스 제공 및 변화 감시)
"serve": "live-server src --watch=src/**/*.html,src/bundle.js,src/**/*.css"

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc && rollup -c",
"serve": "live-server src --watch=src/**/*.html,src/bundle.js,src/**/*.css"
},


15. src 폴더 아래에 index.html 파일 생성 (bundle.js 파일을 참조하여 브라우저에서 표현)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>D3 with TypeScript</title>
</head>
<body>
<script src="/bundle.js"></script>
</body>
</html>


16. 드디어 서비스 시작!! 이제 브라우저에서 index.html 을 열어서 컨텐츠를 확인할 수 있다. 현재는 아무것도 안나올 것이며, F12 개발자도구의 콘솔창을 통해 hello, world 를 확인 할 수 있다.
npm run serve

17. 만약 app.ts 의 내용이 바꾸고 다시 빌드 하면 바뀐 내용을 볼 수 있을 것이다.
npm run build



TS 파일 변화에 대한 감시(Watching)
(지금까지는 ts파일을 변경해도 브라우저에서 내용이 변하지 않았다. 감시기는 JS,HTML 만 감시하지 ts를 감시하지는 않았기 때문인데, 이제 ts 포맷을 감시하도록 해보자)

18. 타입스크립트 내용을 변경 시킬 때마다 build 를 한다는것은 매우 귀찮은 일이다. rollup-watch 를 통해 해결하자.
npm i -D rollup-watch@^4.3.1

19. Pakcage.json 파일에 scripts 에 다음 내용을 추가하자.

"tsc:w": "tsc -w",
"rollup:w": "rollup -c -w",


20. 아래와 같은 명령어를 터미널 3개를 열어서 실행 시키면 app.ts가 바뀌었을때 브라우저도 자동으로 바뀔 것이다. 
npm run serve
npm run tsc:w
npm run rollup:w


Scripts 의 모든 명령을 한방에 해결하기 

21
.  이 모든것을 한방에 하기 위해서는 아래 패키지를 설치한다.

npm i -D npm-run-all@^4.0.2

22.  Package.json 에 아래 "start" 를  추가하고

"scripts": {
...
"start": "npm-run-all --parallel tsc:w rollup:w serve"
},


23
.  npm start 로 실행하면 모든게 한방에 된다. 이제 부터 ts,html 아무것이나 수정해도 브라우저에는 최신 내용이 업데이트 될 것이다.

npm start

D3 로 차트 만들기 

자 이제부터 본론인 데이터 가시화로 들어가 보자. (-.-;;) 

d3.js 는 차트라이브러리가 아니다. d3 는 데이터를 웹페이지 상에서 표현하는것을 도와주는 로우레벨 API 이다. 따라서 손쉽게 차트를 나의 서비스에 추가하고 싶은 분이라면 d3 기반의 좀 더 사용하기 쉬운 라이브러리나 엑셀차트를 사용하는 편이 더 낫다. 내가 d3.js 를 선택한 이유는 내 구미에 맞는 데이터 가시화를 하기 위해서이다. 따라서 d3.js 를 시작하기 위해서는 자바스크립트 및 DOM 이 무엇이며 SVG 가 무엇이지에 대한 기초 지식은 필수이다. 

d3.js 밑바닥 맛보기

아래와 같은 도형의 나열을 d3.js 를 이용하여 표현해보자.

1. HTML 의 body 요소 아래에 svg 요소를 추가하자. (DOM 추가) 

let svg = d3.select("body").append("svg");


2. 해당 svg 요소의 크기를 설정해준다. (attr 를 이용한다. style 을 이용하여 이쁘게 할 수 도 있다)

const w = 960;
const h = 480;

svg.attr("width",w);
svg.attr("height", h);


3. DOM에 바인딩할 데이터를 준비해두고,

let dataset = [5,10,15,20,25];


4. 데이터를 바인딩할 DOM 을 만들어서 HTML 문서에 추가한다. circle 이라는 DOM 들을 선택하고 , dataset 의 크기 만큼  바인딩하는데 ,이때 선택된 DOM 보다 data 가 더 많은 경우 , enter 를 이용해서 임의의 요소를 만든 후에 , circle 이라는 DOM 을 append 해준다라는 의미이다.

- enter에 대한 설명은 요기 참고 -> http://blog.nacyot.com/articles/2015-02-02-d3-selection/

let circle = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")


5. svg 에 circle 요소가 추가되었으니, 이제 circle 에 대한 속성을 추가해보자. circle 이 위치할 x,y 좌표와 반지름을 속성에 넣어준다. 반지름의 경우는 바인딩된 data 의 개별 요소가 익명함수를 이용하여 들어간다.

circle.attr("cx", function(d,i){
return (i * 50) + 25;
})
.attr("cy", h/2)
.attr("r", function(d){
return d;
})


좀 더 그럴싸한 차트를 그려보자.

이 단락에서는 위와 같은 챠트를 얻을 것이다. reddit API를 사용하여 마지막 25개의 포스트에 대한 스코어를 표현 할 것이다.

차트 구성 요약 

기본 차트 구성은 아래와 같을 것이다.

  • the svg (dark background)
  • the plot area (light background)
  • the axes (blue text)
  • the points (orange)

그러기 위해서 우리는 아래와 같은 절차를 밟을 것이다.

  • svg 만들기 
  • plot area 만들기 
  • plot area 에 x-축에 대한 그룹 추가 
  • plot area 에 y-축에 대한 그룹 추가 
  • plot area 에 데이터 포인트들에 대한 그룹 추가 

전체적인 외형 만들기 

import * as d3 from 'd3';
import { redditObject } from './redditFormat';
const width = 960;
const height = 480;
let svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
let plotMargins = {
top: 30,
bottom: 30,
left: 150,
right: 30
};
let plotGroup = svg.append('g')
.classed('plot', true)
.attr('transform', `translate(${plotMargins.left},${plotMargins.top})`);
let plotWidth = width - plotMargins.left - plotMargins.right;
let plotHeight = height - plotMargins.top - plotMargins.bottom;


- 아무것도 없는 HTML 파일에 동적으로 svg 엘리먼트를 추가하고 그것의 크기를 960 / 480으로 설정한다.
- 차트외형은 왼쪽에서 150, 위에서 30 만큼 이동해서 그려준다. plot 로 클래스명 설정

축 만들기 


let xScale = d3.scaleTime()
.range([0, plotWidth]);
let xAxis = d3.axisBottom(xScale);
let xAxisGroup = plotGroup.append('g')
.classed('x', true)
.classed('axis', true)
.attr('transform', `translate(${0},${plotHeight})`)
.call(xAxis);
let yScale = d3.scaleLinear()
.range([plotHeight, 0]);
let yAxis = d3.axisLeft(yScale);
let yAxisGroup = plotGroup.append('g')
.classed('y', true)
.classed('axis', true)
.call(yAxis);

- X,Y 축을 그려준다. 위치등을 자신이 알아서 설정 할 수 있다. 
- X 축은 시간축으로 그리기 위해서 scaleTime() 함수를 이용했고
- Y 축은 일반적인 데이터 크기에 따라서 그려지기 위해 scaleLinear() 를 이용했다. 
- 항상 순서는 scale 설정 ->  axis 에 
scale 을 추가 -> plot 에 axis 추가  방식이다.

데이터 가져와서 바인딩 하기 

d3.json<redditObject>('https://api.reddit.com', (error, data) => {
if (error) {
console.error(error);
} else {
console.log(data);
}
});
let prepared = data.data.children.map(d => {
return {
date: new Date(d.data.created * 1000),
score: d.data.score
}
});
console.log(prepared);

- reddit 데이터를 가져와서 설정해준다. score: d.data.score 
- 시간은 unix timestamp (세컨드 + 소수점) 에서 javascript 시간 타입(밀리세컨드)으로 변경.


스케일/축 업데이트 하기

xScale.domain(d3.extent(prepared, d => d.date))
.nice();
xAxisGroup.call(xAxis);
yScale.domain(d3.extent(prepared, d => d.score))
.nice();
yAxisGroup.call(yAxis);


- 위에서 만든 기존 x,y 축을 데이터에 적합하게 바꾼다. 즉 데이터의 시간과 데이터 값의 min,max 에 따라서 축의 도메인을 변하게 만든다. 

데이터 포인트 드로잉


var dataBound = pointsGroup.selectAll('.post')
.data(prepared);

// delete extra points
dataBound
.exit()
.remove();

// add new points
var enterSelection = dataBound
.enter()
.append('g')
.classed('post', true);

enterSelection.append('circle')
.attr('r', 2)
.style('fill', 'red');

// update all existing points
enterSelection.merge(dataBound)
.attr('transform', (d, i) => `translate(${xScale(d.date)},${yScale(d.score)})`);


- 데이터 별 원형의 반지름2이고 빨강색으로 채워진 도형을 추가해줍니다.


완전한 코드샘플은 여기에 있습니다
 GitHub.


레퍼런스:

https://hstefanski.wordpress.com/2017/07/23/d3-v4-and-typescript-getting-set-up/
https://hstefanski.wordpress.com/2017/08/15/creating-a-chart-with-d3-v4-and-typescript-or-es6/