프로필사진
owgno6
CODELIB
Recent Posts
Recent Comments
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total

티스토리 뷰

D3.js

05 라인 차트(Line Chart)

owgno6 2018. 6. 5. 15:48

이번에는 D3의 차트를 만들면서 정리한다.


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

이전에 작성한 바 차트 중 

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



1
2
3
4
5
6
7
8
9
10
    var line = d3.line()
        .x(function(d) {return xScale(d.x) + 55 ; })
        .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에서는 바 차트나 라인차트를

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



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

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

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

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

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



따라서 라인차트 X값이 0 부터 시작하기에 +55의 수치를 주어 조정하였다. [line 2].

append를 작성해서 

채우기(fill), [line 7].

선의 색(stroke), [line 8].

선의 굵기(stroke-width), [line 9].

형태 [line 10].

를 설정했다.


만약 채우기 값에 색상을 지정하게되면 아래그림 처럼 영역차트가 된다.



   .attr("fill", "royalblue")


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

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

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

따라서 path를 생성하였다 (append). [line 5].

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

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

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





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

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


라인 차트에서는 data 지정시 

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

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


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

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

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

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





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

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

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



SVG 크기도 조절하고 년도에 맞게 데이터도 조정해봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.grid line {
    stroke: lightgrey;
    stroke-opacity: 0.7;
}
.lineChart {
    fill: none;
    stroke: steelblue;
    stroke-width: 1.5px;
}
.lineChart:hover {
    stroke: black;
    stroke-width: 3px;
}
</style>
 
<svg width="700" height="320"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
 
    var series = ["2017""2018"];
 
    var dataset = [ 
        {'1':17'2':27'3':37'4':27'5':17'6':7,  '7':9'8':19'9':29'10':19'11':9'12':0},
        {'1'9'2':19'3':29'4':39'5':29'6':19'7':9'8':7'9':17'10':27'11':17'12':7}];
 
    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]}; })
    });
 
    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 + ")");
 
    var xScale = d3.scalePoint()//scaleBand() scaleOrdinal
        .domain(keys)
        .rangeRound([0, width]);
 
    var yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) { return d3.max(keys, function(key) { return d[key];});})])
        .nice()
        .range([height, 0]);
 
    var colors = d3.scaleOrdinal(d3.schemeCategory20);
 
    svgG.append("g")
        .attr("class""grid")
        .attr("transform""translate(0," + height + ")")
        .call(d3.axisBottom(xScale)
            .tickSize(-height)
        );
 
    svgG.append("g")
        .attr("class""grid")
        .call(d3.axisLeft(yScale)
            .ticks(5)
            .tickSize(-width)
        );
 
    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", line);
 
    var legend = svgG.append("g")
        .attr("text-anchor""end")
        .selectAll("g")
        .data(series)
        .enter().append("g")
        .attr("transform"function(d, i) { return "translate(0," + i * 20 + ")"; });
 
    legend.append("rect")
        .attr("x", width - 20)
        .attr("width"19)
        .attr("height"19)
        .attr("fill", colors);
 
    legend.append("text")
        .attr("x", width - 30)
        .attr("y"9.5)
        .attr("dy""0.32em")
        .text(function(d) { return d; });
 
</script>



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

(1차원 배열이 2차원보다 이해하기 쉽고 구현하기 쉽기 때문에...)


2차원배열


var dataset = [

           [ {x: 1, y: 17}, {x: 2, y: 27}, {x: 3, y: 37}, {x: 4, y: 27}, {x: 5y: 17}, {x: 6, y: 7}, {x: 7, y: 9}, {x: 8, y: 19}, {x: 9, y: 29}, {x: 10, y: 19},{x: 11, y: 9}, {x: 12, y: 0} ],

           [ {x: 1, y: 9}, {x: 2, y: 19}, {x: 3, y: 29}, {x: 4, y: 39}, {x: 5y: 29}, {x: 6, y: 19}, {x: 7, y: 9}, {x: 8, y: 7}, {x: 9, y: 17}, {x: 10, y: 27},{x: 11, y: 17}, {x: 12, y: 7} ]

                            ];



[line 25].

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

1:17, 2:17 와 같이 

각 값을 Json으로 지정해서 1차원배열로 구현했다 

전 시간에 설명했듯이 원소 하나가 큰 Json으로 이룬다.


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

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

dataset 변수에 값만 추가해 주면 

여러 개의 선이 계속 생성된다.

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

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

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


[line 23].

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

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


 

var dataset = [ {'series': '2017', 'A': 10, 'B':20},

                       {'series': '2018', 'A':15, 'B':25} ];



[line 23]과 같이 별도의 배열 변수(series)를 사용하는 것도 가능했다.


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

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

쉬운 이해와 개발을 위해

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

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

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


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

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

json key값들을 추출한다. [line 29].

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

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

keys 변수에 저장한다.


[line 29 ~ 34].

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

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



변환을 위해 

dataset의 개수만큼(2회) 반복(forEach)한다 [line 32]. (여기서부터 클릭시 소스코드로 이동)

그 안에서 각 배열의 원소인 

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

배열을 반환한다 [line 33].

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

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

첫 배열이 만들어질 때,

바 차트 데이터와 같이 

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

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


x축에 1, 2, 3, .... 가 출력되도록

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


지금까지의 앞의 bar 예제와 다르게 

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

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

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

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

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

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


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

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

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


[line 49].

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

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

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

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

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

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

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

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


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

적당히 반올림한 값을 사용하도록 했다. [line 50].


[line 49].

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

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

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


이해하기 어렵다면 전 예제의 bar 차트에서 

변수 yScale의 scaleLinear()에서 

domain과 range부분을 비교해서 보면 도움이 됨.


여기서는 각 라인별로 다른 색을 사용했다 

여기서 사용한 코드와 같이 

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

컬러를 지정해도 된다.

https://github.com/d3/d3-scale



D3에서 지정한 컬러 집합(schemeCategory20)을 사용하지 않고

원하는 색상인, 뚜렷한 컬러지정 값을 넣고 싶을 경우엔 다음 아래와 같이 내용을 추가한다.



var colors = d3.scaleOrdinal(d3.schemeCategory20);

        var colors = d3.scaleOrdinal().range(["IndianRed", "RoyalBlue"]);  


(...전 예제부터 보면 눈치챘겠지만, 개인적으로 그냥 red나 blue보다 약간 indianred나 royalblue처럼 이런 색상이 좋다 ㅎㅎ)


<상위 코드보기>

그리고 CSS를 이용해서 각 선에 마우스를 올리면(over) 

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

보이도록 작성했다 [line 14].


[line 36 ~ 42].

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


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

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

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

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

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

계산하는 것이 척도이니

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

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

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

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


[line 84].

마지막으로 범례(legend)를 구현한다

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

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

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

[line 91 ~ 101].

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

도형(rect)을 생성하고,

어떤 데이터 인지(series)를 

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


https://github.com/zziuni/d3/wiki/API-Reference

https://github.com/d3/d3-scale











the next plan. 라인차트II

                       원형차트



댓글