-
SVG 왕초보와 함께하는 그래프/차트 만들기 - 2. 직선, 곡선 그래프토막글 2021. 8. 2. 13:38
안녕하세요 :)
이번엔 SVG로 곡선 그래프를 만들어본 경험을 공유해보려 합니다.
앞서 SVG 왕초보와 함께하는 그래프/차트 만들기 - 1. 도넛 차트 를 읽고 오신 분이 아니라면, 해당 글에서 설명했던 내용들이 비슷하게 다시 나오기 때문에 도넛 차트를 구현하실 분이 아니더라도 읽고 와주시면 더 이해가 빠를거예요! 다만 d attribute의 A command는 안 쓸거니 스킵하셔도 됩니다. (해당 글 2번 섹션에 있음)
이번에도 결과부터 보자면, 왼쪽의 데이터가 오른쪽 차트로 보이게 됩니다.
구현 순서
구현 순서를 요약해 말해보자면 다음과 같습니다.
요번엔 3번이 고비예요- 데이터를 그래프 좌표로 변환하기
- 직선 그래프 그려보기
- 곡선 그래프를 위한 control point 만들기 (control point가 뭐지...? 느껴지셔도 일단 스킵!)
- 곡선 그래프 그리기
- 애니메이션
1. 데이터를 그래프 좌표로 변환하기
저희가 받은 데이터는 각 날짜 순서대로 소비한 금액이 들어오게 됩니다. 위의 데이터 기준으로, 1일 소비 금액은 20000원, 2일은 100000원, ... 처럼 되는거죠. 그래프는 x 축이 날짜, y축이 금액이므로 해당 좌표계에 맞게 데이터를 좌표로 변환해줘야 합니다.
x축을 먼저 설명해보자면, 만약 한 달의 최대 날짜가 30이라면 각 사이의 간격은 29개가 될 것입니다.
따라서, SVG width를 29개 만큼 나눈 값을 구하고, 거기에 index를 곱한 값이 x 좌표가 됩니다.
y축에 해당하는 데이터는 가격입니다. 이 가격을 데이터 내의 최대 값으로 나누면 데이터 내의 percentage 가 될 것입니다. 여기에 SVG 의 height을 곱하면 y좌표가 되겠죠! 코드로 옮기면 다음과 같습니다.
getCoordinates에 data를 넣고 얻은 결과는 아래와 같습니다.
2. 직선 그래프 그려보기
앞서 getCoordinates 함수로 우린 데이터의 좌표를 얻었습니다. 이 좌표로 직선 그래프를 그려보도록 하죠!
우린 path d attribute의 M, L 명령어를 사용할 것입니다.
SVG 의 <line> 태그를 사용해서도 이를 표현할 수 있겠지만 해당 차트에선 이를 표현하려면 29개의 line 태그가 필요하게 될 것입니다.
path의 d attribute의 M, L 명령어를 사용하면 한 줄로 표현할 수 있습니다.
M (x, y) 는 시작점을 정해주고, L (x, y)는 해당 좌표로 선을 그을 수 있게 됩니다.
그렇다면 데이터의 좌표를 순회하며 처음 좌표라면 M 명령어를, 나머지 좌표는 L 명령어를 추가해주면 되겠군요!
아래는 코드와 그 결과값입니다 :)
3. 곡선 그래프를 위한 Control Point 만들기
사실 저 정도만 해도 저희는 행복합니다. 그러나 아시죠?
저는 곡선으로 그래프를 표현하고 싶었고, 이는 재앙의 시작이었습니다.
근데 사실 그렇게 안어려워요 ^,.^d attribute 에선 Bezier curve를 그릴 수 있게 해주는 C 명령어가 있습니다.
이 명령어는 다음 6개의 인자를 받습니다. C (x1, y1, x2, y2, x, y)
x, y는 우리가 익히 아는 목표 지점입니다. 그렇다면 (x1, y1), (x2, y2)는 뭘까요?
이는 bezier curve의 control point를 지정해주는 값입니다.
아래 그림과 같이, bezier curve를 그리기 위해선 총 4개의 점이 필요합니다.
우린 시작, 끝점을 이미 알고 있으므로, 나머지 2개의 점이 필요한데요. 해당 점을 Control Point라 지칭합니다.
아마 해당 링크로 들어가셔서 몇 번 만져보시면 바로 감이 오실겁니다.
이 점을 구하기 위해선 다음과 같은 절차가 필요합니다.
우리는 S1 (E0), E1 (S2) 를 알고 있습니다.
즉, 끝점은 시작점의 다음 점, 시작점은 끝점의 이전 점이 되는거죠. (말이 어렵죠... 이것 말고 다른 표현이 생각이 안나네요 ㅜ)
자세히 보시면, CP1은 S0에서 S2로 이은 선과 평행한 선 위에 있습니다. 이를 양의 방향으로 일정 거리에 해당하는 점이 CP1인 거죠!
CP2는 E0에서 E2로 이은 선과 평행하며 반대로 음의 방향으로 일정 거리에 해당하는 점입니다.
이 일정거리를 얼마나 늘리느냐에 따라 더 smooth 한 곡선이 만들어지게 됩니다.
아까 말한 S0, S2로 이은 선과, E0, E2로 이은선을 opposedLine이라고 지칭합니다. 그리고 opposedLine의 길이, x축으로부터의 각도를 구해야 합니다.
한번 코드로 옮겨보죠!!
피타고라스의 정리에 의하여 length는 각 좌표의 x, y 길이의 제곱의 제곱근입니다.
각도는 atan2 method를 사용하면 쉽게 구할 수 있습니다 :)
해당 정보들을 사용해 Control Point 를 구하면 됩니다!
앞서 말했다시피 Contorl Point는 두개가 있는데, 만약 끝 CP 일 경우 음의 방향으로 만들어 주어야 합니다. 따라서 angle에 Math.PI를 곱해줍니다.
4. 곡선 그래프 그리기
드디어 곡선 그래프를 그립니다!!
우리는 앞선 연산들로부터 C command를 위한 모든 정보를 이미 알고 있습니다.
시작점, 끝점, Control Point 1, 2를 모두 알고있죠.
이를 나열하기만 하면 됩니다!!
우선 path의 d 속성이 가질 값을 구하는 코드를 보도록 하죠.
각각 control point start X, Y, End X, Y, 끝점을 넣어주면 됩니다.
그리고 return 값을 d value 값에 넣어주면 됩니다 :)
아래는 곡선 차트를 그리는 함수입니다.
style은 stroke-width 때문에 곡선이 차트 바깥으로 빠져 나오게 되므로, 이를 조정하기 위한 스타일 코드입니다.
5. 애니메이션
거의 끝나갑니다 !!
해당 글을 보시기 전에 SVG 왕초보와 함께하는 그래프/차트 만들기 - 1. 도넛 차트 를 읽어보셨다면 stroke-dasharray, stroke-dashoffset에 대해 이해하고 계실 것입니다. 이번에도 이 속성을 사용해 구현합니다.
원리는 같습니다. path의 길이만큼 stroke-dashoffset을 당겨놓았다가, 0만큼 이동시키면 됩니다.
문제는 글에서는 percentage 가 있었기 때문에 해당 percentage 만큼 호의 길이를 알아낼 수 있었습니다.
그러나 이번에는 해당 정보가 없습니다 :( 우리는 어떻게 path의 길이를 알아내야 할까요?
path 내에는 getTotalLength() 라는 메소드가 있습니다.
해당 메소드를 사용하면 path 의 길이를 알 수 있고, 우리는 해당 길이만큼 dashoffset을 주었다가 0으로 돌려주면 되겠죠!
아래는 해당 코드입니다 :)
stroke-dasharray 속성을 주고, dashoffset을 path.getTotalLength() 만큼 주고 0으로 돌려줍니다.
드디어 우리가 원했던 곡선 차트를 완성했습니다~~
마무리
예시 gif 에 있는 기준선은 굳이 설명하지 않겠습니다. line 태그를 이용해 쉽게 만들 수 있습니다.
몇일 전까지만해도 SVG 생초보였는데, 앞의 도넛 차트와 곡선 그래프를 구현하며 약간의 자신감을 얻을 수 있었습니다 :)
참고 자료
'토막글' 카테고리의 다른 글
TypeScript 4.6 Release Note (0) 2022.03.10 야 너두 자동 배포 할 수 있어 with AWS CodeDeploy (0) 2021.08.18 SVG 왕초보와 함께하는 그래프/차트 만들기 - 1. 도넛 차트 (1) 2021.08.02