프로필사진
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

티스토리 뷰


일반적인 차트에서 흔히 보는 척도를 구현하려고 한다.

이러한 기능을 구현하기 위해서 앞의 '01 간단한 차트 만들기' 

마지막 소스 중에서 다음과 같이 수정해 준다.


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
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
    border: 1px solid;
}
.bar {
    fill: orange;
}
.bar:hover {
    fill: tomato;
    cursor: pointer;
}
.text {
    fill: white;
    font-weight: bold;
}
</style>
<svg width="500" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
    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} ];
 
    var svg = d3.select("svg");
 
 
    svg.selectAll("rect")
         .data(dataset)
         .enter()
         .append("rect")
             .attr("class","bar")
             .attr("height"function(d, i) {return (d.y * 5)})
             .attr("width"40)
             .attr("x"function(d, i) {return (50 * i)})
             .attr("y"function(d, i) {return (250 - d.y * 5)});
 
    svg.selectAll("text")
         .data(dataset)
         .enter()
         .append("text")
         .text(function(d) {return d.y})
             .attr("class""text")
             .attr("x"function(d, i) {return 50 * i + 10})
             .attr("y"function(d, i) {return 250 - d.y * 5 + 15});
 
</script>
cs


차트의 가로 축(X) 데이터와 세로 축(Y) 데이터의 값이 출력되는 것이

표면적으로 봐도 편하고 보기에도 좋다.

기본적으로 차트에서 일반적으로 제공되는 기능이기도 하다.

따라서, 이전에는 데이터를 세로 축(Y)의 값만 배열로 작성했지만

가로 축(X)에도 값을 출력하기 위해 임의의 값 dataset을 Json데이터의 배열로 작성했다. [line 22~28]

X축의 값과 Y축의 값이

같이 하나의 쌍으로 구성된다.

 


이러한 데이터의 구조 변화에 따라서

사용법에 조금 변화가 있다. [line 38, 41, 47, 50]


바의 높이와 위치를 계산할 때

변수 d로 받아서 사용하던 데이터 사용법을 d.y의 형태로 바꿔준다.

기존에 각각 function에서 넘어오는 값은 각 배열의 원소(d)가 넘어와서

이 배열 원소는 그냥 값이니 그대로 사용했다.

즉, d * 5와 같이 바로 사용했었다.

하지만 Json배열은 각 원소는 Json이 되고

Json은 x, y로 지정되어 있기 때문에 

return에 d,y * 5와 같은 방식으로 사용되게 된다. 


그리고 CSS를 이용하여 SVG의 border를 변경해본다. [line 4]



다음에는 SVG 크기에 맞춰서

X축부터 출력해본다.


기능을 구현하기 전에 D3에서 제공하는 scale에 대해서 알아야 한다.

[참조URL]

http://blog.naver.com/PostView.nhn?blogId=zero_kjy&logNo=220788807530

https://www.slideshare.net/neuroassociates/week16-d3js


v3버전에서는 scale(척도)과 axix(축)을 나누어서 구현했다.

v4버전에서는 scale 하나로 처리하는 차이가 있다.


v4에 대한 자료를 대충만 봐도...

많은 척도가 정의되어 있다.

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


이 중에서 가장 많이 쓰이는 scaleBand라는 척도가 있다는 것만 기억하고 넘어간다.


척도에서 중요한 개념인 domain과 range가

d3.v3와 d3.v4에서 같은 개념과 방식으로 사용된다.


    

        d3.scale()

        척도 : 입력되는 정의역(domain)과 출력되는 치역(range)을 매핑한 함수


        .domain() : 입력되는 데이터 값의 범위

        .range() : 출력되는 범위 (단위:픽셀)


        d3.scale.linear()

                .domain([시작점,끝점])

                .range([시작점,끝점)]


        var xAxis = d3.svg.axis()     //축을 생성하는 함수

                .scale(x)                     //축을 생성할 때는 어떤 척도를 다뤄야 하는지 알려줘야 함.

                .orient("bottom");       //축의 위치




예로, dataset = [1, 2, 3, 4, 5] 값을 가지는 데이터가 있고

출력하고자 하는 차트 SVG 너비가 100px이라고 하면,

    .domain([1,5])

    .range([1,100])

이 된다.

그리고, 척도(scaleBand)에서

데이터 값이 1일때는 20px, 2일때는 40px과 같은

적당한 위치값을 계산해서 반환하게 된다.

크기는 x축이니 width로 bandwidth란 메소드를 이용하고

위치는 척도의 인스턴스(xScale) 함수를 호출하면 된다.



다음은 위 지식을 가지고 다음 코드를 작성해 X축을 출력해 본다.




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
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
    border: 1px solid;
}
.bar {
    fill: orange;
}
.bar:hover {
    fill: tomato;
    cursor: pointer;
}
.text {
    fill: white;
    font-weight: bold;
}
</style>
<svg width="500" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
    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} ];
 
    var svg = d3.select("svg");
    var width  = parseInt(svg.style("width"), 10);
    var height = parseInt(svg.style("height"), 10)-20;
    var xScale = d3.scaleBand()
         .domain(dataset.map(function(d) { return d.x;} ))
         .range([0, width]).padding(0.2);
 
    svg.selectAll("rect")
         .data(dataset)
         .enter()
         .append("rect")
             .attr("class","bar")
             .attr("height"function(d, i) {return (d.y * 5)})
             .attr("width", xScale.bandwidth())
             .attr("x"function(d, i) {return xScale(d.x)})
             .attr("y"function(d, i) {return (height-d.y*5)});
 
    svg.selectAll("text")
         .data(dataset)
         .enter()
         .append("text")
         .text(function(d) {return d.y})
             .attr("class""text")
             .attr("x"function(d, i) {return xScale(d.x) +25})
             .attr("y"function(d, i) {return height-d.y*5 + 15});
 
    svg.append("g")
         .attr("transform""translate(0," + (height) + ")")
         .call(d3.axisBottom(xScale));
 
</script>
cs


[line 33 ~ 35].

눈금을 표시하기 위한 X축 스케일 설정이다. 

domain에 지정하는 데이터 값이 

연속된 값이면 시작값과 종료값(최대값)을 지정하면 된다.

여기서는 A, B, C 등의 문자열이기 때문에

배열(dataset)에 사용된 값 모두를 나열하여(map) 지정하였다.

range의 화면 범위를 지정하기 위해

SVG의 CSS width값을 구해서 지정하였다 (svg.style("width")). 


[line 43 ~ 44].

바를 생성하면서(append("rect"))

바의 크기(attr-width)와 위치(attr-x)를 척도를 이용해서 지정한다.

이렇게 척도를 이용하여 차트의 크기(SVG)나 

입력되는 데이터의 개수에 따라 적절한 도형(rect)이 생성되게 된다.

배열 개수를 바꿔서 확인 할 수 있다.

즉, SVG에 맞춰서 적절하게 도형이 생성됨.


[line 56 ~ 58].

마지막으로 척도를 이용하여 축(axis)을 작성한다.

X축이니 화면(SVG) 바닥(bottom)에 놓고

위치는 화면의 높이(height = Height-20)가 된다.(눈금자위치) [line 32].

   

        (height = Height-5)                                    (height = Height-20)


height에 20px을 뺀 것은 X 축의 값을 출력하는 공간을 계산한 것이다.

즉, translate를 이용하여 X 좌표는 0, Y 좌표는 height인 지점에 

라인을 그리고 축의 값(xScale)을 출력한다.


지금까지 사용하지 않은 그룹 개념이 사용되었다 (svgG.append("g")). 

눈금(tick)은 여러 개의 선으로 구성되기 때문이다.

즉, 눈금 개수(tick count) 만큼 SVG 선(Line)이 생성된다.

이것을 간편하게 관리하기 위해 그룹으로 관리한다.

웹 브라우저의 개발자 도구(F12)로 확인하면 

SVG group (X축)에 여러 개의 눈금(Line)이 있는 것을 확인할 수 있다.



       svg.append("g")                                

               .attr("class", "x axis")                                            

               .attr("transform", "translate(0," + (height) + ")")        

               .call(xAxis);

  

        // g 문서요소     

        - 그룹을 뜻함.

        - 다른 문서 요소를 담는 역할.

        - 트랜스폼 요소 적용 가능. 

        // call 함수  
        - 선택된 변수의 내용을 불러온다.





이번에는 다음 그림과 같이 Y축도 구현해 본다.



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
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
    border: 1px solid;
}
.bar {
    fill: orange;
}
.bar:hover {
    fill: tomato;
    cursor: pointer;
}
.text {
    fill: white;
    font-weight: bold;
}
</style>
<svg width="500" height="300"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
    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}];
 
    var svg = d3.select("svg");
    var width  = parseInt(svg.style("width"), 10) -30;
    var height = parseInt(svg.style("height"), 10-20;
    var svgG = svg.append("g")
        .attr("transform""translate(30, 0)");
    var xScale = d3.scaleBand()
        .domain(dataset.map(function(d) { return d.x;} ))
        .range([0, width]).padding(0.2);
    var yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d){ return d.y; })])
        .range([height, 0]);
 
    svgG.selectAll("rect")
        .data(dataset)
        .enter()
        .append("rect")
            .attr("class""bar")
            .attr("height"function(d, i) {return height-yScale(d.y)})
            .attr("width", xScale.bandwidth())
            .attr("x"function(d, i) {return xScale(d.x)})
            .attr("y"function(d, i) {return yScale(d.y)});
 
    svgG.selectAll("text")
        .data(dataset)
        .enter()
        .append("text")
        .text(function(d) {return d.y})
            .attr("class""text")
            .attr("x"function(d, i) {return xScale(d.x)+xScale.bandwidth()/2})
            .style("text-anchor""middle")
            .attr("y"function(d, i) {return yScale(d.y) + 15});
 
    svgG.append("g")
        .attr("transform""translate(0," + (height) + ")")
        .call(d3.axisBottom(xScale));
 
    svgG.append("g")
        .call(d3.axisLeft(yScale)
                  .ticks(5));
 
 
</script>
cs

[line 38 ~ 40]
X축은 척도로 scaleBand를 사용한 반면,
Y축은 척도로 scaleLinear를 사용하였다.
X축의 값은 A, B, C 등의 문자열이었고,
Y축의 값은 1, 2, 3 등으로 연속된 숫자 값이다.
즉, 고정된 문자열에는 scaleBand, 일반 숫자값은 scaleLinear을 사용한다.
domain과 range는 모든 척도에서 같은 개념으로 사용된다.
다만, Y축이 숫자이기 때문에 
0 부터 주어진 배열내 최대값(max(d.y))을 domain에 지정한다.


range 사용에서도 X축과 차이가 있다.
X축에서는 0부터 width로 지정했고
Y축에서는 height에서 0으로 지정했다. 
즉, 큰 값이 먼저 나왔다.
X축은 A가 왼쪽, 즉 x좌표의 값이 가장 작다.
Y축은 1이 가장 하단, 즉 차트의 높이(Height) 값, 가장 큰 값을 가진다.
따라서, Y축은 domain 지정시 작은 값에서 큰 값으로
range 지정은 큰 값에서 작은 값으로 지정하여 서로 매핑 시켜준다.

         // 눈금을 표시하기 위한 스케일 설정

var yScale = d3.scale.linear()  // 스케일 설정
   .domain([ 0, dataMax ])       // 원래크기
   .range([ dataMax, 0 ])         // 실제 출력 크기




[line 64 ~ 67].
마지막으로 X축은 axisBottom으로 하단에 축을 그리고
Y축은 axisLeft로 좌측에 축을 그린다.
다만, X축은 height값에 -20을 줘서 축의 위치를 변경했지만
Y축은 -30을 줘서 축의 위치를 변경했다.
(translate(30, 0))의 값과 같게 이동한 것이다.
이부분은 아래 더 자세하게 설명할 것이다.


[line 66].
코드 작성이 완료 되었지만
이상의 코드에는 x축 생성할때 사용한 코드와 차이가 있다.
기존 X축에 사용한 방식으로 Y축 코드를 작성하면 
다음 그림에서 왼쪽 하단처럼
X축과 섞여서 이상하게 보인다.



이 문제를 해결하기 위해
차트 전체를 구성하는 그룹을 만들고 (svgG = svg.append("g")) [line 33].
그룹 자체를 오른쪽으로 30 px 정도 이동시켜준다 (translate(30, 0)). [line 34].
그리고, 차트를 위해 생성되는 도형(rect), 라벨(text), 척도등을 
이전에는 svg에 생성했으나 svgG에 생성한다.


Y축에는 척도의 개수(tick)을 5개로 지정하였다. [line 68].
지정하지 않으면 D3에서 자동으로 지정한다.

         .ticks(5);

        - 축의 눈금 개수 지정

        - 개수에 따라 눈금값이 정확히 떨어지지 않으면 입력한 눈금개수 중 

           값이 정확히 떨어지는 개수로 자동으로 조정해서 보여줌.




[line 58 ~ 59]

추가로 차트에 출력된 값을 보면 앞 차트와 다른 점이 있다.

숫자가 bar(도형)의 정중앙에 출력된다.

(이전 예제는 19, 29와 같은 두자리 숫자와 9와 같은 한자리 숫자의 출력 좌표가 조금 안 맞다)

이전 예제는 도형의 중앙을 계산하기 어려웠지만

척도를 이용하여 간단하게 계산할 수 있다.

도형을 출력할 좌표(xScale)에 도형 크기(width)의 반(/2)을 더해주면

숫자가 찍힐 정확한 중앙 지점을 계산할 수 있다.

(위 코드에서 파란색으로 표시된 코드를 의미한다.)

SVG의 Text 테그의 text-anchor 스타일을 middle로 지정하면

지정된 좌표가 문자열의 중앙에 오도록 출력해 준다.


             

 <before>                                                     <after>





척도와 관련된 구현을 마쳤다.
데이터 개수를 추가해서 실행해 보면 
그림과 같이 bar(도형)의 크기가 바뀌면서 실행된 것을 볼 수 있다.


척도를 이용함으로써

다양한 데이터에 유동적으로 반응하는 결과를 만들 수 있다.






'D3.js' 카테고리의 다른 글

06 라인 차트(Line Chart) - 툴팁(Tooltip), 꺾은선(Curve Line)  (0) 2018.06.11
05 라인 차트(Line Chart)  (0) 2018.06.05
04 툴팁(Tooltip)  (2) 2018.06.04
03 그리드(Grid)  (0) 2018.06.01
01 간단한 도형 차트(Bar Chart) 만들기  (1) 2018.05.25
댓글