D3로 bar chart 그리기

시작하기

d3포스트에서 다루었던 기본을 토대로 bar차트를 만들어 보도록 하겠습니다.

축그리기

처음 html파일을 생성하고, 다음과 cdn d3를 불러와줍니다.

1
2
3
4
5
6
7
8
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.js"></script>
</head>
<body></body>
</html>

가장 먼저 svg, svg의 사이즈를 추가해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.js"></script>
</head>
<body>
<script>
const svgWidth = 300,
svgHeight = 300;
const data = [40, 80, 130, 210, 100, 20];
const svg = d3
.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border", "1px solid rgba(0,0,0,0.1)");
</script>
</body>
</html>

X축그리기

먼저 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
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.js"></script>
</head>
<body>
<script>
const svgWidth = 300,
svgHeight = 300;
const data = [40, 80, 130, 210, 100, 20];

const svg = d3
.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border", "1px solid rgba(0,0,0,0.1)");

const padding = 30;

// xAxis
const xAxisScale = d3
.scaleBand()
.domain(data.map((d, i) => i)) // 실제값의 범위 index값
.range([padding, svgWidth - padding]) // 변환할 값의 범위
.padding(0.1); // 내부 padding
const xAxis = d3.axisBottom().scale(xAxisScale);

const xAxisTranslate = svgWidth - padding;
svg
.append("g")
.call(xAxis)
.attr("transform", `translate(0, ${xAxisTranslate})`);
</script>
</body>
</html>
  • xAxisScale: axis축을 그려줄 범위를 정해줍니다. 이 변수값은 함수입니다. console.log(typeof xAxisScale) // function
  • xAxis: axis 아랫방향으로 scale을 적용한 축을 그립니다.console.log(typeof xAxis) // function
  • xAxisTranslate: 왼쪽 상단부터 시작되기때문에 translate로 세로 높이에 패딩을 뺄셈한 만큼 이동해주어야합니다.
    이후 svg에 call함수로 xAxis를 실행시켜줍니다.

x축

Y축그리기

y축도 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
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.js"></script>
</head>
<body>
<script>
const svgWidth = 300,
svgHeight = 300;
const data = [40, 80, 130, 210, 100, 20];

const svg = d3
.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border", "1px solid rgba(0,0,0,0.1)");

const padding = 30;

// xAxis
const xAxisScale = d3
.scaleBand()
.domain(data.map((d, i) => i)) // 실제값의 범위
.range([padding, svgWidth - padding]) // 변환할 값의 범위
.padding(0.1);
const xAxis = d3.axisBottom().scale(xAxisScale);

// yAxis
const yAxisScale = d3
.scaleLinear()
.domain([0, d3.max(data)]) // 실제값의 범위
.range([svgHeight - padding, padding]); // 변환할 값의 범위(역으로 처리)
const yAxis = d3.axisLeft().scale(yAxisScale);

const xAxisTranslate = svgWidth - padding;
svg
.append("g")
.call(xAxis)
.attr("transform", `translate(0, ${xAxisTranslate})`);
svg
.append("g")
.attr("class", "x-axis")
.call(yAxis)
.attr("transform", `translate(${padding}, 0)`);
</script>
</body>
</html>
  • yAxisScale: axis축을 그려줄 범위를 정해줍니다. 이 변수값은 함수입니다. console.log(typeof yAxisScale) // function
  • yAxis: axis 왼쪽방향으로 scale을 적용한 축을 그립니다.console.log(typeof yAxis) // function

y축

data 바인딩

data값을 바형태의 차트로 그릴수 있도록 하겠습니다.

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
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.js"></script>
</head>
<body>
<script>
const svgWidth = 300,
svgHeight = 300;
const data = [40, 80, 130, 210, 100, 20];

const svg = d3
.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border", "1px solid rgba(0,0,0,0.1)");

const padding = 30;

// xAxis
const xAxisScale = d3
.scaleBand()
.domain(data.map((d, i) => i)) // 실제값의 범위
.range([padding, svgWidth - padding]) // 변환할 값의 범위
.padding(0.1);
const xAxis = d3.axisBottom().scale(xAxisScale);

// yAxis
const yAxisScale = d3
.scaleLinear()
.domain([0, d3.max(data)]) // 실제값의 범위
.range([svgHeight - padding, padding]); // 변환할 값의 범위(역으로 처리)
const yAxis = d3.axisLeft().scale(yAxisScale);

const xAxisTranslate = svgWidth - padding;
svg
.append("g")
.attr("class", "x-axis")
.call(xAxis)
.attr("transform", `translate(0, ${xAxisTranslate})`);
svg
.append("g")
.attr("class", "y-axis")
.call(yAxis)
.attr("transform", `translate(${padding}, 0)`);

// data
const rectWidth = 40;
svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("height", (d) => svgHeight - yAxisScale(d) - padding)
.attr("width", xAxisScale.bandwidth())
.attr("x", (d, i) => xAxisScale(i))
.attr("y", (d) => yAxisScale(d))
.attr("fill", "orange");
</script>
</body>
</html>

selectAll로 rect를 선택한다고 선언한뒤에 data를 enter하여 엘리먼트가 생성되고 각각에 __data__값이 저장 됩니다. 각 사각형의 width, height, x좌표, y좌표를 데이터 기준으로 뿌려줍니다.

  • height: data의 값입니다.
  • width: 앞에 xAxisScale에서 너비(xAxisScale.bandwidth())를 가져올수 있습니다. 각각 계산해서 구현할 수도 있지만, 연결 되어있는 편이 관리하기 더 편리합니다.
  • x: width와 마찬가지로 xAxisScale에서 사용한 위치(xAxisScale(i))를 가져올수 있습니다. x축과 키값이 같아야합니다.
  • y: yAxisScale(d) yAxis 값에 연동됩니다.

결과물

j쿼리와 비슷한 사용법이라 쉽게 차트를 그릴수 있었습니다.

추가 - hover했을때 transition과 text

hover했을때 간단한 트랜지션과 데이터 값 텍스트가 나오게 해보겠습니다.

처음으로 text가 나올 부분인 g태그 하나 추가합니다.
text가 기본상태에서는 나오지 않게 display none을 넣어주었습니다.

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
// ...
svg
.append("g")
.attr("class", "x-axis")
.call(xAxis)
.attr("transform", `translate(0, ${xAxisTranslate})`);
svg
.append("g")
.attr("class", "y-axis")
.call(yAxis)
.attr("transform", `translate(${padding}, 0)`);
svg.append("g").attr("class", "val"); // text영역

// data
const rectWidth = 40;
svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("height", (d) => svgHeight - yAxisScale(d) - padding)
.attr("width", xAxisScale.bandwidth())
.attr("x", (d, i) => xAxisScale(i))
.attr("y", (d) => yAxisScale(d))
.attr("fill", "orange");
// 텍스트값 추가
svg
.select(".val")
.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", (d, i) => xAxisScale(i) + xAxisScale.bandwidth() / 2)
.attr("y", (d) => yAxisScale(d))
.text((d) => d)
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("display", "none");
//...

마우스 이벤트를 추가합니다.
마우스 hover되었을때와 나왔을때의 기능을 구현합니다.

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
// ...
svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("height", (d) => (d) => svgHeight - yAxisScale(d) - padding)
.attr("width", xAxisScale.bandwidth())
.attr("x", (d, i) => xAxisScale(i))
.attr("y", (d) => yAxisScale(d))
.attr("fill", "orange")
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut);
//...
function onMouseOut(d, i) {
d3.select(this).transition().duration(400).style("fill", "orange");
d3.select(".val")
.selectAll("text")
.filter((d, index) => index === i)
.attr("display", "none");
}

function onMouseOver(d, i) {
d3.select(this).transition().duration(400).style("fill", "red");
d3.select(".val")
.selectAll("text")
.filter((d, index) => index === i)
.attr("display", "block");
}

결과물

최종코드

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
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.js"></script>
</head>
<body>
<script>
const svgWidth = 300, svgHeight = 300;
const data = [420, 80, 130, 210, 510, 80];

const svg = d3.select("body")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border","1px solid rgba(0,0,0,0.1)");

const padding = 30;

// xAxis
const xAxisScale = d3.scaleBand()
.domain(data.map((d,i) => i)) // 실제값의 범위
.range([padding, svgWidth - padding]) // 변환할 값의 범위
.padding(0.1);
const xAxis = d3.axisBottom().scale(xAxisScale);

// yAxis
const yAxisScale = d3.scaleLinear()
.domain([0, d3.max(data)]) // 실제값의 범위
.range([svgHeight - padding, padding]); // 변환할 값의 범위(역으로 처리)
const yAxis = d3.axisLeft().scale(yAxisScale);

const xAxisTranslate = svgWidth - padding;
svg.append("g").attr("class", "x-axis").call(xAxis).attr("transform", `translate(0, ${xAxisTranslate})`);
svg.append("g").attr("class", "y-axis").call(yAxis).attr("transform", `translate(${padding}, 0)`);
svg.append("g").attr("class", "val");

// data
const rectWidth = 40;
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("height", d => svgHeight - yAxisScale(d) - padding)
.attr("width", xAxisScale.bandwidth())
.attr("x", (d,i) => xAxisScale(i))
.attr("y", d => yAxisScale(d))
.attr("fill", "orange")
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut)

svg.select(".val")
.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", (d,i) => xAxisScale(i) + (xAxisScale.bandwidth() / 2))
.attr('y', d => yAxisScale(d))
.text(d => d)
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
.attr("text-anchor", "middle")
.attr("display", "none");

function onMouseOut(d,i){
d3.select(this)
.transition()
.duration(400)
.style("fill", "orange");
d3.select('.val')
.selectAll("text").filter((d,index) => index === i)
.attr("display", "none");
}

function onMouseOver(d,i){
d3.select(this)
.transition()
.duration(400)
.style("fill", "red");
d3.select('.val')
.selectAll("text").filter((d,index) => index === i)
.attr("display", "block");
}
</script>
</body>
</html>
Share