라이브러리 없이 그래프, 차트 만들기

2023년 5월 08일
alt

도넛 차트와 그래프를 만드는 방법?

HTML & CSS, Canvas, SVG??

흔히 차트나 그래프 등을 만들 때 라이브러리를 사용하지만, 나는 라이브러리를 상요하지 않고 구현하고 싶었다.

  1. HTML,CSS루 구현하는 방법 일반 tag를 사용해서 구현하는 방법 으로 막대 그래프 같은 경우 widthheight로 기본 모양을 만들고 배열로 height가 그래프의 값이 되고 이러한 방법을 구현 한다. 도넛 차트 같은 경우 radius를 통해 굴절되는 값을 조절하여 구현한다. 이 방식의 단점은 화면의 크기에 상관없이 차트나 그래프가 고정되어 반응형으로 구현하기 어려움이 있습니다.
  2. Canvas로 구현하는 방법 자바스크립트 코드로 화면 위에 픽셀을 그려 넣는 방법인데, 아직 Canvas를 사용해 본적이 없었고, 단점으로 도형의 크기에 따라 해상도를 고려해야 하는 단점이 있었다.
  3. 마지막으로 SVG로 구현하는 방법으로 SVG태그는 벡터 형식으로, 화면에 크기에 따라 유동적으로 변한다는 특징이 있습니다. 그리고 viewBox라는 속성으로 반응형도 쉽게 구현할 수 있습니다. 또한 SVG 태그 중에 circle, rect 등의 태그와 stroke, stroke-width, stroke-dasharray 등의 속성으로 도형을 쉽게 표현할 수 있다는 장점 있어, SVG 태그를 활용해 그래프와 차트를 구현하기로 했습니다.

도넛 차트 만들기

circle, stroke-dasharray

먼저 circle 태그와 stroke-dasharray 속성에 대해서 알아보겠습니다.

circle 태그는 원의 모양을 만드는 태그로 우리가 흔히 div 등으 태그를 활용해서 radius를 조절해서 원을 만들거나, 모서리 부분을 둥글게 만들지만 circle 태그를 이용하면 원형 모양을 만들 수 있다.

stroke-dasharray 속성은 MDN 공식문서에서 모양의 윤곽을 칠하는 데 사용되는 대시 및 간격의 패턴을 정의하는 표시 속성입니다. 라고 설명해주고 있다. 즉, 도형의 둘레의 선과 선 길이, 선의 간격을 패턴으로 정의합니다.

기본적으로 stroke-dasharray의 기본 값은 0으로 설정되어 있고, 밑에 예시로 circle 태그를 활용해서 원을 구현했다.

<svg viewBox='0 0 100 100'>
  <circle
    cx='50'
    cy='50'
    r='45'
    fill='none'
    stroke='blue'
    strokeWidth='10'
    strokeDasharray='0'
  />
</svg>
alt

이러한 형태로 원을 그리고 둘레 선을 원하는 길이와 간격으로 패턴을 정할 수 있다. 위에 사진에 예시를 통해 값이 10이라는 것은 선의 길이와 간격이 모두 10으로 설정한다는 의미이고, 오른쪽 상단에 10 5라는 값은 선의 길이는 10이고 선의 간격이 5를 의미한다. 길이와 간격을 입력할때 ,를 사용해도 된다.

여기서 만약에 strokeDasharray="50 10" 이러한 형태로 적용한다면, 도형의 길이까지만 적용되고, 길이를 넘어서면 잘리게 됩니다.

또한, 주의할 점은 circle 태그의 stroke-dasharray 패턴은 시계 3시 방향으로 시작한다는 특징이 있습니다.

stroke-dashoffset

이제 도넛 모양의 차트를 만들건데 stroke-dasharray만큼 중요한 것이 stroke-dashoffset이다, offset은 css에서 위치를 설정할 때 쓰는 속성같은건데, offset은 상대 주소의 개념으로 위치를 이동할 기준이 필요하다.

circle 태그 같은경우 stroke-dasharray가 시계 3시반향에서 시작되니 기본 값으로 3시 방향이 기준으로 offset을 설정해서 위치를 조정할 수 있다.

stroke-dashoffset를 이용해서 우리가 흔히 생각하는 도넛차트의 모양으로 만들어 줄것이다.

구현해보기

alt
<svg viewBox='0 0 300 300'>
  <circle
    cx='100'
    cy='100'
    r='90'
    fill='none'
    stroke='beige'
    strokeWidth='15'
  />
  <circle
    cx='100'
    cy='100'
    r='90'
    fill='none'
    stroke='blue'
    strokeWidth='15'
    strokeDasharray={`${2 * Math.PI * 90 * 0.25} ${
      2 * Math.PI * 90 * (1 - 0.25)
    }`}
    strokeDashoffset={2 * Math.PI * 90 * 0.25}
  />
</svg>

위 도넛 차트는 파란색 값이 25%되는 차트를 구현했습니다. 현재 두개의 circle 태그를 사용해서 첫번째 circle태그는 배경을 역할을 하고, 두번째 circle태그가 차트의 값을 보여줍니다.

여기서 중요한 것은 stroke-dasharray값과 stroke-dashoffset입니다. 공통적을 쓰고 있는 2 * Math.PI * 90 이값은 우리가 학창시절에 배운 원의 둘레를 구하는 공식입니다. circle 속성안 r이라는 값이 반지름입니다.

그리고 stroke-dasharray는 선의 길이와 선의 간격을 설정한다고 했을 때 2 * Math.PI * 90 * 0.25 이 값이 둘레의 25% 값이고, 2 * Math.PI * 90 * (1 - 0.25) 이값이 75%만큼 간격을 넓혀서 하나의 선으로만 보여주게 하는 것입니다.

마지막 stroke-dashoffset의 값은 2 * Math.PI * 90 * 0.25으로 설정되어 있는데, stroke-dashoffset은 선의 위치를 조정한다고 했고, circle태그의 특징으로 시계 3시방향에서 시작한다고 했을때 360도25%90도 만큼 뒤로 올려주어 12시 부터 시작할 수 있게 해줍니다. 반대로 -값으로 하면 앞으로 이동하겠지요. 이렇게 도넛차트를 구현할 수 있습니다.

도넛차트 정리 stroke-dasharray은 선의 길이와 선의 간격을 조절할 때 사용하는 속성이고, 도넛 차트를 만들 때 보여줄 값을 25%라고 한다면 2 * Math.PI * r * 0.25으로 선의 길이를 적용하고 그 간격을 조절 할때 나머지 값인 2 * Math.PI * r * 0.75 이렇게 적용하면 25% 만큼 선이 있고 나머지 간격이 75%만큼 있는 모양이 된다. 간단하게 공식으로 만들면 A라는 값이 우리가 도넛 차트에 보여줄 값이라면 stroke-dasharray="(2 * Math.PI * r * A) (2 * Math.PI * r * (1-A))" 이런 식으로 간단하게 만들 수 있다.

stroke-dashoffset는 선의 위치를 조정하고 +일때 뒤로가고, -일때 앞으로 이동한다. 역시 2 * Math.PI * r * 원하는 각도로 값을 설정해주면 된다. circle태그의 특징으로 시계 3시 방향에서 시작하니 2 * Math.PI * r * 0.25으로 하면 12시 방향에서 시작한다.

막대 그래프 만들기

rect, hight, x, y

막대 그래프를 만들기 위해서 SVG태그 안에 rect라는 태그를 활용해서 구현을 할겁니다. MDN에서 요소는 위치, 너비, 그리고 높이로 정의된 사각형을 그리는 기본 SVG 모양입니다. 이 직사각형은 둥근 모서리를 가질 수 있습니다. 이렇게 설명해주고 있네요.

alt
<svg width='100%' style={{ margin: "50px" }} height='300'>
  <rect width='40' height='300' fill='blue' y='0' x='0' />
  <rect width='40' height='300' fill='blue' y='20' x='100' />
  <rect width='40' height='300' fill='blue' y='70' x='200' />
  <rect width='40' height='300' fill='blue' y='30' x='300' />
  <rect width='40' height='300' fill='blue' y='10' x='400' />
  <rect width='40' height='300' fill='blue' y='60' x='500' />
</svg>

위 사진과 코드를 보고 설명할게요. 앞서 설명한 것처럼 rect는 사각형 모양을 만드는 태그입니다. 그 안에 다양한 속성이 있지만 width, height, y, x 속성이 저는 가장 중요하다고 생각해요. 먼저 width, height는 사각형의 가로와 세로 길이를 정하고, y는 y축 기준으로 +는 아래로 -위로 이동할 수 있습니다.(이동한다, 그만큼 비워진다 다양하게 표현할 수 있을 것 같네요.) 그리고 x역시 좌우로 +오른쪽으로 -는 왼쪽으로 이동이 가능합니다.

여기서 재밌는 점은 밑에 사진 입니다.

alt
<svg width='100%' style={{ margin: "50px" }} height='1000'>
  <rect width='40' height='300' fill='blue' y='0' x='0' />
  <rect width='40' height='300' fill='blue' y='20' x='100' />
  <rect width='40' height='300' fill='blue' y='70' x='200' />
  <rect width='40' height='300' fill='blue' y='30' x='300' />
  <rect width='40' height='300' fill='blue' y='10' x='400' />
  <rect width='40' height='300' fill='blue' y='60' x='500' />
</svg>

코드를 보면 높이값을 1000px을 주고 있습니다. 참고로 px은 생략이 가능했습니다. 다시 사진을 다시보면 SVG의 높이가 1000px이고 그만큼 rect 태그가 y 값 만큼 내려온 것을 볼 수 있어요.

이런식으로도 막대 그래프를 표현할 수 있을것 같네요.

막대 그래프의 값을 주는 방법

막대 그래프를 구현하는 것은 생각보다 어렵지 않앗습니다. 하지만 여기서 그래프의 값을 어떻게 줘야 원하는 높이의 그래프를 구현할 수 있을까 고민을 하게 됩니다.

이 생각은 도넛 차트에서 힌트를 얻었는데요. stroke-dasharray에서 선의 길이와 선의 길이의 간격을 조절해서 도넛 모양의 차트를 구현할 때 막대 그래프에서는 heighty을 이용하는데, height값은 고정이고, y의 값을 이용해서 원하는 길이의 차트를 구현하려면 만약 원하는 값이 A일 때, y=(height-A) 이런식으로 높이와 차트에 줄 높이의 차이를 y값에 적용하면 제가 원하는 모양이 나왔죠.

그리고 또 하나의 문제점은 x의 간격이 문제 였는데 이 역시도 arrayindex를 사용해서 구현했습니다.

const barChart = [{y:0},{y:280},{y:230},{y:270},{y:290},{y:240}]
<svg width='100%' style={{ margin: "50px" }} height='1000'>
  {barChart.map((data,index) => (
   <rect width='40' height='300' fill='blue' y={300-data.y} x={100*index} />
  ))}
</svg>

이런식으로 구현하면 좀 더 간편하게 구현할 수 있고, 부가적으로 이 블로그르 처음 이미지에 막대 그래프 하단에 textSVG태그 중에서 text라는 태그를 사용해서 구현했습니다.

애니메이션

도넛 차트와 막대 그래프 모두 애니메이션을 구현을 했는데, 제가 생각했을 때 애니메이션 구현 방법은 크게 두가지 인거 같습니다.

  • 첫 번째 : 차트나 그래프의 값을 카운터가 올라가는 형식으로 받아서 구현한다.
  • 두 번째 : 차트나 그래프 위에 tag를 추가해 그 tag사라지는 방식으로 구현한다.

첫 번째 방식은 도넛 차트에서 사용하였다. 나는 값을 카운터가 올라가는 형태로 구현을 했는데, 애니메이션으로도 간단하게 구현을 할 수 있었다.

.AnimatedCircle {
  animation: circle-fill-animation 2s ease;
}

@keyframes circle-fill-animation {
  0% {
    stroke-dasharray: 0 calc(2 * 3.14 * 90px);
  }
}

이러한 방법으로 stroke-dasharray에 애니메이션을 추가해서 구현할 수 있었다. 이것을 보고 CSS의 학습도 아직 부족하다고 생각했다.

두 번째 방법으로 구현하는 것은 막대 그래프에 적용했다. 막대 그래프가 바닥에서 차트가 올라오는 애니메이션을 구현하고 싶었는데, CSS로는 구현을 하지 못했고, 막대 그래프 위에 하나의 그래프를 더 만들어 그 그래프를 위에서 부터 없어지는 애니메이션을 구현해서, 실제 모습은 막대 그래프가 아래에서 위로 값이 채워지는 애니메이션으로 구현하였다.

뜻밖의 발견 animate

alt

블로그 글을 쓰면서 자료를 찾다가 SVG태그 안에 자체 적으로 animate 태그를 활용해서 CSS로 스타일을 주지 않고 애니메이션을 구현하는 방법이 있다는 것을 발견했다..!!

앞서 프로젝트를 진행할 때 당연히 SVG로 구현하고 CSS로 애니메이션을 구현했는데, animate 태그가 있다니..

앞서 말했던 애니메이션 구현 방법을 통해서 구현해도 되지만 자체 animate 태그를 활용하는 방법도 좋을 것 같다.

<svg width='150' height='200'>
  <rect width='100' height='100'>
    <animate
      attributeName='y'
      from='100'
      to='0'
      dur='2s'
      repeatCount='indefinite'
    />
  </rect>
</svg>

위에 코드를 보면 rect 태그 안에 animate 태그를 추가할 수 있습니다. 여기서 attributeName은 어떤 속성값에 애니메이션을 추가 할건지 선택하는 속성입니다. rect에서는 x, y, height, width 등이 있구요. xy는 위, 아래 방향이구, heightwidth는 속성 그대로 가로, 세로의 길이입니다.

circle에서 attributeName 속성은 r, cx, cy 등이 있습니다. r은 반지름, cxcy는 위, 아래 방향이라고 생각하면 됩니다.

그 밖에 fromto는 시작과 끝의 값을 설정하는 것이고 durduration으로 지속시간을 의미합니다. 마지막으로 repeatCount는 지속 여부 등을 설정할 수 있습니다.

막대 그래프 정리 막대 그래프는 rect의 사각형 모양의 태그를 이용해서 구현하고, height,y값을 통해서, 원하는 값의 높이를 설정하는데, y값은 +일 때 밑으로 내려가고 -일때 위로 올라간다. 그래서 높이가 300pxrect 태그가 있고, 원하는 값이 270이라면 30만큼만 내려주면 되니까, y=300-270 이런식으로 구현할 수 있고, x의 값을 통해 rect의 좌우 간격을 조절할 수 있는데 이는 배열을 통해 x=50*index이러한 방식으로 간격을 일정하게 조절할 수 있다.

그리고 애니메이션은 크게 두가지 방법이 있는데, 하는 직접 CSS를 통해 애니메이션을 주는 방법이 있고, 또 하나는 SVG태그 안에 animate태그를 활용해서 애니메이션 효과는 주는 방법이 있다.

회고

점진적 과부하 프로젝트는 라이브러리를 사용하지 않고 구현을 하고 싶었고, 그렇게 SVG 태그를 활용해서 구현 할 수 있었다. 그리고 그래프를 구현할 때 알지 못했던 animate라는 tag도 알 수 있어서 나중에 이것도 활용할 수 있을 것 같다.

처음 사용했던 victory.js라는 차트 라이브러리가 많이 복잡한 이유가 있던거 같다. 직접 구현해보니, 생각보다 어려운 부분도 있었다. 타이머 처럼 도넛 차트가 채워지는 기능도 있었고, 막대 그래프가 아래에서 위로 올라오는 애니메이션도 구현하려고 시간을 많이 썼다. text태그의 위치 잡는법, 막대 그래프 hover했을 때 값을 말풍선으로 보여주고, 높이를 맞추는게 어려웠다.

다음에 차트나 그래프를 구현할 때 좀 더 잘해보자!

참고 자료