canvas로만 그래프 그리기

하성화·2022년 3월 2일
0

카트라이더 API를 사용하여 유저의 기록 그래프를 라이브러리를 사용하지 않고 구현해 보았다.
일단 틀을 먼저 그려주어야 한다.

const ctx = chartRef.current?.getContext("2d");
ctx && drawBackgrond(ctx);
 const drawBackgrond = (ctx) => {
   // 전체 박스 그리기
   ctx.beginPath();
   ctx.moveTo(GRAPH_LEFT, GRAPH_BOTTOM);
   ctx.lineTo(GRAPH_RIGHT, GRAPH_BOTTOM);
   ctx.lineTo(GRAPH_RIGHT, GRAPH_TOP);
   ctx.moveTo(GRAPH_LEFT, GRAPH_BOTTOM);
   ctx.lineTo(GRAPH_LEFT, GRAPH_TOP);
   ctx.stroke();
    ...
  };

순서대로 왼쪽 아래에서 오른쪽 아래 선을 그리고 (밑변)
오른쪽 아래에서 위로 선을 그린다. (오른쪽 변)
그 다음 왼쪽아래에서 왼쪽 위로 선을 그린다. (왼쪽 변)
이렇게 그리면 사각형에서 윗변만 없는 컵모양이 완성된다.

그 후 수평선을 하나씩 그려준다.

ctx.moveTo(GRAPH_LEFT, GRAPH_TOP);
ctx.lineTo(GRAPH_RIGHT, GRAPH_TOP);

// 3/4 선
ctx.moveTo(GRAPH_LEFT, (GRAPH_HEIGHT / 4) * 3 + GRAPH_TOP);
ctx.lineTo(GRAPH_RIGHT, (GRAPH_HEIGHT / 4) * 3 + GRAPH_TOP);

// 중앙선
ctx.moveTo(GRAPH_LEFT, GRAPH_HEIGHT / 2 + GRAPH_TOP);
ctx.lineTo(GRAPH_RIGHT, GRAPH_HEIGHT / 2 + GRAPH_TOP)

// 1/4 선
ctx.moveTo(GRAPH_LEFT, GRAPH_HEIGHT / 4 + GRAPH_TOP);
ctx.lineTo(GRAPH_RIGHT, GRAPH_HEIGHT / 4 + GRAPH_TOP);

다 그리면 다음과 같은 그래프 틀이 완성된다.

다음으로 가져온 데이터들을 좌표값으로 바꾸어 주어야 한다.
가져온 값은 해당 맵에 대한 기록이며, 단위는 ms이다.
데이터 예시 : [129102, 137186, 130546, 135648, 133593, 131973, 136481, 132300, 132747, 134662]

if (graphData) {
  const min = Math.min(...graphData);
  const max = Math.max(...graphData);
  const len = Math.floor(GRAPH_WIDTH / graphData.length + 1);
  let point = [];

  graphData.map((data, index) => {
    point = [
      ...point,
      {
        x: index !== graphData.length - 1 ? index * len + 22 : GRAPH_WIDTH,
        y:
        data === min ? 0 : Math.floor(((data - min) / (max - min)) * GRAPH_HEIGHT),
      },
    ];
  });
  setPoints(point);
}

x좌표값은 일정하게 나누기 위해 전체 너비에서 해당 데이터의 길이만큼 나누었다.
y좌표값을 계산하기 위해 max값과 min값을 구한 후.
map을 돌며 해당 값에 대한 상대적인 y좌표 값을 구하였다.
위의 데이터를 변환하면 다음과 같은 좌표값이 만들어 진다.

ctx.beginPath();
for (var i = 1; i < points.length; i++) {
  ctx.lineTo(points[i].x, points[i].y);
}

위의 좌표값을 반복문을 통해 선을 그려주면 다음과 같은 결과가 나온다.

이제 마지막으로 값에대한 데이터를 적어주기만 하면 끝난다.

ctx.fillText(matchTimeTimeExtractor(min).slice(0, 4), 0, 140);
ctx.fillText(
  matchTimeTimeExtractor(min + (max - min) * 0.25).slice(0, 4),
  0,
  110
);
ctx.fillText(
  matchTimeTimeExtractor(min + (max - min) * 0.5).slice(0, 4),
  0,
  75
);
ctx.fillText(
  matchTimeTimeExtractor(min + (max - min) * 0.75).slice(0, 4),
  0,
  40
);
ctx.fillText(matchTimeTimeExtractor(max).slice(0, 4), 0, 9);

5개의 수평선에 값을 주기위해 맨 아래의 값은 최고 기록인 최소 값 맨 위의 값은 제일 좋지 않은 기록인 최대 값을 주었고 가운데 값들은 최소 값에서 상대적인 위치값을 더해주었다.

라이브러리 없이 만드려다보니 시간이 오래 걸리고 생각할 것이 많았지만 그만큼 배운 것 같아서 뿌듯하다.

0개의 댓글