이번에는 D3 (Data-Driven Documents)의 가장 큰 특징인

편리한 데이터 조작 관련 기능을

라인(Line) 차트를 만들면서 정리한다.


먼저, 라인 차트의 구현 방법을 알기 위해

이전에 작성한 바 차트 예제 중

하나를 골라서 마지막에 다음 코드를 추가한다.

var line = d3.line()
    .x(function(d) {return xScale(d.x); })
    .y(function(d) {return yScale(d.y); });
  
svg.append("path")
    .data([dataset])
    .attr("fill", "none")
    .attr("stroke", "blue")
    .attr("stroke-width", "1.5px")
    .attr("d", line);

전체코드

코드를 추가하고 실행해 보면

그림과 같이 라인 차트가 생성되는 것을 볼 수 있다.

즉, D3에서는 바 차트나 라인 차트를

간단하고 유사하게 생성하고 관리할 수 있다.


바 차트는 각각의 데이터에 대하여

각각의 도형(rect)를 생성하지만,

라인 차트는 각각의 선(데이터)이 이어져 하나의 선(Path)이 그려진다.

따라서 path를 생성하였다 (append).

그리고 각각의 선은 line 함수(인스턴스- d3.line())를 호출해서 그리게 된다.

라인 함수는 하나의 선 전체(path)에 대한 속성을 지정하는 부분과

각각의 선(line)에 대한 속성을 지정하는 부분으로 구성된다.


데이터(data) 사용법에도 차이가 있다.

바 차트에서는 data 지정시 배열을 그냥 넘겼다.

라인 차트에서는 data 지정시

배열(dataset)을 다시 배열([dataset])로 지정했다.

즉, 2차원 배열로 변환 한 것이다.

1 차원 배열 하나가 하나의 선(path)이 되기 때문이고,

2 차원으로 여러 개의 값을 지정하면 여러 개의 선이 생성된다.

라인은 여러 개의 선이 있다고 전제하고 있다.

2차원 배열을 기본으로 사용한다.


선의 색(stroke)은 파란색(blue)

선의 굵기(stroke-width)는 1.5px로 지정했다.

채우기(fill)은 없는 것(none)으로 했는데

채우기에 값(컬러)을 지정하면 영역 차트(Area Chart)가 된다.


그림을 보면 선의 시작 (A값) 부분이 축을 벗어나 있다.

바 차트는 Y축에서 일정 부분 떨어져서 시작하고

라인 차트는 Y축에 딱 붙어서 시작한다.

선의 위치(x)에 bandwidth를 추가해도 되지만

라인 차트 고유의 처리법으로 해결한다.


지금까지는 바 차트에 라인을 추가해서

간단하게 라인 차트와의 차이점을 살펴 봤다.

이제부터 그림과 같이 라인 차트에 맞는 예제를 구현한다.

또, D3의 특징인 데이터 사용법도 익히고

개인적으로 보다 직관적으로 이해 할 수 있다고 여겨

앞서 사용했던 Json(x,y)으로 구성된 1차원 배열을

2차원 배열로 구현하지 않고,

키와 값으로 구성한 Json 1차원 배열로 바꾸어서 진행한다.


사용할 데이터는 다음 표에서 작성한 것과 같다.

바 차트에서는 다음 표의 한 행이 사용되었던 것이고,

X: A, Y: 9와 같이 X, Y라는 키(key)로 구성된 Json을 이용했다.

이렇게 데이터를 구성해서 다음 표와 같은 데이터를 구성하려면

2차원 배열로 구현해야 한다.

(제시된 표 자체가 2차원 배열)

 

A

B

C

D

E

E

F

2016

9

19

29

39

29

19

9

2017

17

27

37

27

17

7

0


이것을 기존 방식으로 작성하면 다음과 같다.

var dataset = [
    [{x:'A', y: 9}, {x:'B', y:19}, {x:'C', y:29}, {x:'D', y:39}, {x:'E', y:29}, {x:'F', y:19}, {x:'G', y:9 }],
    [{x:'A', y:17}, {x:'B', y:27}, {x:'C', y:37}, {x:'D', y:27}, {x:'E', y:17}, {x:'F', y: 7}, {x:'G', y:0 }]
];


이러한 2차원보다 1차원이 이해하기 쉽고 구현하기 쉽기 때문에

하나의 행이 하나의 데이터(선-path)가 되게 하기 위해

A: 9, B: 19 와 같이

각 값을 Json으로 지정해서 1차원으로 구현했다 [라인 31].

(데이터 조작에 대한 연습을 위한 것도 있다.)

var dataset = [ {'A': 9, 'B':19, 'C':29, 'D':39, 'E':29, 'F':19, 'G':9},
                {'A':17, 'B':27, 'C':37, 'D':27, 'E':17, 'F':7, 'G':0} ];

배열의 원소 하나가 큰 Json으로 하나의 차원역할을 한다.

바 차트에서는 x, y라는 키가 고정인 것이고

라인 차트에서는 A, B, C등 키가 유동적인 차이가 있다.


표를 다시 확인해 보면 데이터 행이 2개이다.

즉, 두 개의 선이  생성되는 예제이다.

dataset 변수에 값만 추가해 주면

여러 개의 선이 계속 생성된다 [라인 31].

이렇게 여러 개의 선(행) 각각을 시리즈(series)라고 한다.

여기에서는 2016년과 2017년 두 개의 데이터(행),

두 개의 시리즈를 사용한다.


인터넷에서 구할 수 있는 예제들은

다음과 같이 시리즈를 데이터와 같이 구성하는데

여기서는 이해를 위해 별도의 배열 변수(series)를 사용했다 [라인 29].

var dataset = [ {'series': '2016', 'A': 9, 'B':19, 'C':29, 'D':39, 'E':29, 'F':19, 'G':9},
                {'series': '2017', 'A':17, 'B':27, 'C':37, 'D':27, 'E':17, 'F':7, 'G':0} ];

이렇게 데이터를 받아서

series 배열 변수로 분리 코드를 작성하는데

여기서는 미리 분리하고 작성하였다.


앞서 정리한 바 차트에 라인을 추가한 예제에서

라인은 2차원 배열을 기본으로 한다고 정리했다.

쉬운 이해와 개발을 위해

1차원 배열로 구현했기 때문에

이것을 2차원 배열로 변환하는 작업이 필요하다 [라인 34~39].

(처음부터 2차원으로 구현하는 것이 더 좋을 수도 있다.)


1차원의 dataset을 2차원의 data 변수로

변환하기에 앞서해 d3의 keys 함수를 이용하여

json key값들을 추출한다 [라인 34].

여기서는 각 시리즈의 데이터 개수가 같다는 전제로

첫 번째 (dataset[0]) 행의 json key값들을 추출하여

keys 변수에 저장했다.

(dataset[1]에 값이 더 있다면 출력되지 않는 버그가...)

이렇게 생성한 keys 변수는

일단 데이터 개수를 파악하기 위해서도 사용하고

X축에 척도를 출력하기 위해서도 사용한다 [라인 50].


다음 코드는 1차원으로 지정된 데이터(dataset)를

2차원의 데이터(data)로 변환하는 코드이다.

var keys = d3.keys(dataset[0]);
var data = [];

dataset.forEach(function(d, i) {
    data[i] = keys.map(function(key) { return {x: key, y: d[key]}; })
});

변환을 위해

dataset의 개수만큼(2회) 반복(forEach)한다 [라인 37].

그 안에서 각 배열의 원소인

Json의 개수만큼 반복(keys.map) 해서.

배열을 반환한다 [라인 38].

반환된 배열을 다시 배열 (data[i])에 넣으면서

2차원 배열이 만들어진다.

첫 배열이 만들어질 때,

바 차트 데이터와 같이

{A: 9}가 {x: ‘A’, y: 9}로 변환되어 저장된다 ({x: key, y: d[key]}).

바 차트 예제 데이터와 같이 진 것이다.


이렇게 구성한 데이터를

척도에 넣어주기만 하면 중요한 작업은 끝났다.

설명이 부족할 수도 있고,

어려울 수 있는 부분이지만

이 부분이 가장 중요한 부분이고

D3의 가장 큰 특징 중 하나라고 생각하니

꼭 이해하고 넘어가야 한다.


x축에 A, B, C ~~가 출력되도록

이 값을 가지고 있는 keys를 xScale에 데이터로 지정하였다 [50라인].

앞의 예제와 다르게

xScale에 scaleBand가 아닌 scalePoint가 사용되었다.

실행 결과로 짐작할 수 있겠지만,

이 둘은 고정된 값을 처리하는 공통점이 있지만,

scalePoint는 0부터 시작하는 차이가 있다.

즉, y축에 딱 붙어서 시작된다.

앞의 예에서 문제가 된 라인의 시작 위치가 해결된 것을 볼 수 있다.


Y축은 다소 복잡해 보이는데

데이터 변환과 유사한 방식으로 구현한 코드이다 [53라인].

D3의 max 함수는 주어진 배열내의 최대값을 찾아준다 [54라인].

첫 max에 dataset을 지정하면 2개의 json 데이터가 반환되고

다시 각각에 대하여 max를 keys 개수만큼 반복한다.

keys 개수만큼 반복하는 이유는

각 json 데이터 개수를 의미 하기 때문이다.

이렇게 반복해서 키에 해당하는 값(d[key])을 반환하고

이중에 가장 큰 값(max)이 반환되고,

반환된 2개의 큰 값 중 가장 큰 값이 반환되어

척도 구성을 위한 domain의 값으로 지정한다.

그리고 이 domain의 소수점이 너무 많으면

적당히 반올림한 값을 사용하도록 했다(nice).


이 부분이(54라인),

즉 데이터내의 최대값 찾기가

처음부터 2차원 데이터를 사용하지 않고

1차원 데이터를 사용한 이유 중의 하나이다.

얼마나 복잡한지 앞서 제시한 2차원 배열을 이용해서

이 부분을 구현해 보길 바란다.

(이런걸 해보는 것이 실력 향상의 지름길...)


여기서는 각 라인별로 다른 색을 사용했다 [라인 57, 85].

여기서 사용한 코드와 같이

D3에서 지정한 컬러 값 집합(schemeCategory20)을 사용해도 되고,

첫 예제에서 작성한 것처럼 각 컬러를 지정해도 된다.

(이것이 바챠트 마지막에 제시된 문제의 답이다.)

var colors = d3.scaleOrdinal(d3.schemeCategory20);
var colors = d3.scaleOrdinal().range(["red", "orange", "yellow", "green", "blue", "indigo", "violet"]);

D3에서 지정한 컬러 값에 대한 자세한 정보는

D3 문서페이지 맨 마지막에 있다.


여기까지가 라인 차트를 구현하기 위해

필요한 코드들이고,

나머지는 차트를 보기 좋게 하기 위한 코드들이다.

먼저, CSS를 이용해서 각 선에 마우스를 올리면(over)

선을 강조하기 위해 더 두껍게 (3px)

보이도록 작성했다 [라인 15].


41 ~ 47 라인 코드는

SVG내에서 차트를 중앙에 놓기 위한 코드이다.

var margin = {left: 20, top: 10, right: 10, bottom: 20};
var svg = d3.select("svg");
var width = parseInt(svg.style("width"), 10) - margin.left - margin.right;
var height = parseInt(svg.style("height"), 10)- margin.top - margin.bottom;

var svgG = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

즉, 상하좌우에 여백(margin)을 주는 코드이다.

왼쪽(left)과 상단(top)은 주어진 값만큼

전체 구조를 이동(transform)시키면 된다.

우측(right)과 바닥의 여백(bottom)은

차트를 생성할 때 빼고 계산하면 된다.

계산하는 것이 척도이니

X축 계산에 사용되는 width 값을

여백 만큼 (-margin.left - margin.right) 줄여주고

Y축 계산에 사용되는 height 값을

여백 만큼(-margin.top - margin.bottom) 줄여주면 된다.


마지막으로 범례(legend)를 구현한다 (라인 88~).

범례는 차트 내에서 적당한 위치(우측 상단)에

각 라인이 나타내는 의미를 보여 준다.

(위치는 지정하기 나름이다.)

각 라인은 색으로 구분하기 때문에

도형(rect)을 생성하고,

어떤 데이터 인지(series)를

문자로(text)로 출력해 주면 된다.


마지막 예제로 그림과 같이 툴팁 기능을 구현한다.


구현할 툴팁 기능은

바 차트에서는 도형(rect)에 마우스를 올리면

해당하는 값을 보이게 했다.

라인 차트는 하나의 선이기 때문에 이렇게 구현할 수 없다.

대체 방법은 X축의 값이 있는 부분에

적당한 도형(rect, circle등)을 생성하고(append)

이 도형에 마우스를 올리면 툴팁을 보이도록 작성한다.

반지름(r)이 3 px인 원을 생성하였다.

보이고 숨기는 방식은 바 차트와 동일하다.

코드를 복사해서 그대로 사용했다.


<style>
.toolTip {
    position: absolute;
    border: 1px solid;
    border-radius: 4px 4px 4px 4px;
    background-color: yellow;
    padding: 5px;
    text-align: center;
    font-size: 11px;
    min-width: 30px;
}
</style>

var line = d3.line()
    //.curve(d3.curveBasis)
    .x(function(d) { return xScale(d.x); })
    .y(function(d) { return yScale(d.y); });


var lineG = svgG.append("g")
    .selectAll("g")
    .data(data)
    .enter().append("g");

lineG.append("path")
    .attr("class", "lineChart")
    .style("stroke", function(d, i) { return colors( series[i]); })
    .attr("d", function(d, i) {return line(d); });

lineG.selectAll("dot")   
    .data(function(d) {return d })
    .enter().append("circle")                               
        .attr("r", 3)       
        .attr("cx", function(d) { return xScale(d.x) })        
        .attr("cy", function(d) { return yScale(d.y);})   
        .on("mouseover", function() { tooltip.style("display", null); })
        .on("mouseout",  function() { tooltip.style("display", "none"); })
        .on("mousemove", function(d) {
            tooltip.style("left", (d3.event.pageX+10)+"px");
            tooltip.style("top",  (d3.event.pageY-10)+"px");
            tooltip.html(d.x + "<br/>" + d.y);
        });   
       
var tooltip = d3.select("body").append("div").attr("class", "toolTip").style("display", "none");

전체코드

다만, 앞서 제작한 두 번째 예제는

값에 따라 휘어지는 라인이 부드럽다 (curveBasis).

이렇게 부드러운 상태에서

정확한 값에 도형(dot)을 출력하면

선과 도형의 위치가 맞지 않게 된다.

따라서 선도 그림과 같이 딱 맞추어서 출력되게 하기 위해

부드러운 선이 아닌 꺾인선으로 구현해야 한다.

.curve(d3.curveBasis)를 지우면

기본 설정이 적용되어

그림과 같이 꺾인 선으로 출력된다.


이상으로 라인 챠트와 관련된 내용을 정리했지만

실제로는 데이터를 다루는 방법에 대하여 강조하였다.

이유는 D3의 특징이기도 하고

이 데이터 처리 방식(위 예제에서)에

다시 rect로 도형을 생성하면 Grouped bar 차트가 되기 때문이다.

즉, 데이터 처리와 구성이 중요하다는 의미이다.

따로 정리하지 않지만

이상의 예제를 다시 Grouped bar차트로 구현한 소스를 공유한다.

     groupedBar1.html
     groupedBar2.html
     groupedBar3.html
     groupedBar4.html




앞서 사용된 데이터가 다음 표와 같았다.

 

A

B

C

D

E

E

F

2016

9

19

29

39

29

19

9

2017

17

27

37

27

17

7

0


이것을 여기서는  다음과 같은 데이터로 구성해서 코드를 작성했다.

var dataset = [ {'A': 9, 'B':19, 'C':29, 'D':39, 'E':29, 'F':19, 'G':9},
                 {'A':17, 'B':27, 'C':37, 'D':27, 'E':17, 'F':7, 'G':0} ];

하지만 다음과 같이 지정하는 방법이

더 직관적이고, 많이 사용하는 방법(예 c3js)이다.

이렇게 구현해 보면 실력향상에 도움이 될 것이다.

var dataset = [ {'A', 'B', 'C', 'D', 'E', 'F', 'G'},
                { 9,  19,  29,  39,  29,  19,  9},
                {17,  27,  37,  27,  17,   7,  0} ];




'JavaScript > Chart' 카테고리의 다른 글

1. D3 (SVG) 차트 만들기 - Bar I  (0) 2017.04.15
2. D3 (SVG) 차트 만들기 - Bar II  (0) 2017.04.15

+ Recent posts