본문 바로가기
Study/js

javascript canvas로 허접한 꺾은선 그래프 만들기

by 개발새-발 2021. 4. 15.
반응형

canvas에서 간단하게 꺾은선 그래프를 그려보았다.

 

See the Pen DrawGraph by Jh Do (@kazakan) on CodePen.

 

가로 세로 축이 그려져있고 주어진 값에 점이 찍혀 있으며 그 점을 잇는 선들이 있는 전형적인 꺾은선 그래프이다. 이 그래프는 점 근처에 마우스를 가져다 대면 그 점이 가리키는 값을 보여준다. 그 외에는 별 기능은 없다.

 

코드 살펴보기

코드를 차근차근 살펴보자.

// data
data = [5, 3, 26, 7, 6, 3, 45, 7, 54, 3, 10, 4, 3, 20,54];

const cvs = document.getElementById("cvs");
const ctx = cvs.getContext("2d");

cvs.height = window.innerHeight;
cvs.width = window.innerWidth;

// mouse position
mx = 0;
my = 0;

그래프에 그릴 값들과 캔버스에 대한 것들, 마우스 포인터 값을 저장할 변수를 만들어 주었다. 캔버스의 크기는 window의 높이,넓이로 지정해주었는데, 이 때문에 꽉찬화면으로 그리는것이 가능하다.

 

function draw() {
  const pad = 50;
  const chartInnerWidth = cvs.width - 2 * pad;
  const chartInnerHeight = cvs.height - 2 * pad;
  
  ctx.moveTo(pad, pad);
  ctx.lineTo(pad, pad + chartInnerHeight);
  ctx.stroke();

  ctx.moveTo(pad, pad + chartInnerHeight);
  ctx.lineTo(pad + chartInnerWidth, pad + chartInnerHeight);
  ctx.stroke();

그래프축의 가로,세로축을 그린다. pad만큼 여유를 두고 그린다.

  max = Math.max(...data);
  min = Math.min(...data);
  nX = data.length;
  nY = max-min+1;

  blockWidth = chartInnerWidth / (nX + 1);
  blockHeight = chartInnerHeight / (nY + 1);

그래프를 그릴 때, 가장 아래에 0이 아닌 그래프의 최소값, 가장 위에는 최대값이 그려지게 할 것이다. 그래서, 그려질 값들의 최대, 최소 값을 구하였다. 위에 결과물을 보면, 축에 칸 같은것이 그려져있다. 최대, 최소값과 그릴 값들의 개수에 따라서 축에 그려야 할 한칸의 길이가 달라질 것이다. 이를 계산하는 코드이다.

  // drawing ticks
  const ticklenhalf = 5;
  for (i = 1; i < nX + 1; ++i) {
    ctx.moveTo(pad + i * blockWidth, pad + chartInnerHeight - ticklenhalf);
    ctx.lineTo(pad + i * blockWidth, pad + chartInnerHeight + ticklenhalf);
    ctx.stroke();
  }

  for (i = 1; i < nY + 1; ++i) {
    ctx.moveTo(pad - ticklenhalf, pad+chartInnerHeight - i * blockHeight);
    ctx.lineTo(pad + ticklenhalf, pad+chartInnerHeight - i * blockHeight);
    ctx.stroke();
    ctx.font = "15px Arial";
    ctx.textAlign = "right";
    ctx.textBaseline = "middle";
    ctx.fillText((min+i-1).toString(),pad-20,pad+chartInnerHeight - i * blockHeight);
  }

방금말한 언급한 칸의 개수를 이용해서 축에 표시하는 코드이다. 세로축에는 숫자가 같이 그려진다.

  // where to draw
  x = pad + blockWidth;
  y = pad + chartInnerHeight - blockHeight * (data[0]-min+1);

  xOnCvs.push(x);
  yOnCvs.push(y);

  for (i = 1; i < nX; ++i) {
    xOnCvs.push(pad + (i + 1) * blockWidth);
    yOnCvs.push(pad + chartInnerHeight - blockHeight * (data[i]-min+1));
  }

그래프에 그려질 data값들을 canvas상의 좌표로 변환해주는 코드이다.

function drawlines() {
    ctx.fillStyle = "black";
    ctx.strokeStyle = "black";
    x = xOnCvs[0];
    y = yOnCvs[0];

    ctx.beginPath();
    ctx.arc(x, y, 5, 0, 2 * Math.PI);
    ctx.fill();

    for (i = 1; i < nX; ++i) {
      nextx = xOnCvs[i];
      nexty = yOnCvs[i];

      ctx.moveTo(x, y);
      ctx.lineTo(nextx, nexty);
      ctx.stroke();

      ctx.beginPath();
      ctx.arc(nextx, nexty, 5, 0, 2 * Math.PI);
      ctx.fill();

      x = nextx;
      y = nexty;
    }
  }

draw 함수 내부에 주어진 data값들을 그래프에 그려주는 drawlines를 만들었다. 작은 원으로 data의 위치를 표시해주고, 이를 선으로 잇는다. 구현하고나서 보니 굳이 drawlines를 내부함수로 만들 필요는 없었던 것 같다.가

  for (i = 0; i < nX; ++i) {
    dx = xOnCvs[i] - mx;
    dy = yOnCvs[i] - my;
    ctx.font = "30px Arial";
    if (dx * dx + dy * dy < 100) {
      ctx.fillStyle = "rgba(77, 82, 82,100)";
      ctx.fillRect(xOnCvs[i], yOnCvs[i] - 40, 40, 40);
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillStyle = "rgb(213, 219, 219)";
      ctx.fillText(data[i].toString(), xOnCvs[i] + 20, yOnCvs[i] + 20 - 40);
    }
  }
  drawlines();
}

결과물에서 마우스를 점 근처에 다가가면 점이 나타내는 값이 보일것이다. 이 코드는 마우스와 점 사이의 거리를 계산하여 특정 거리 안에 있다면 값을 canvas에 적는다. 여기까지가 draw의 내용이다.

 

window.addEventListener("resize", function () {
  cvs.width = window.innerWidth;
  cvs.height = window.innerHeight;

  draw();
});

화면 크기가 달라질 때마다 캔버스의 크기를 다시 정하고, 다시 그린다.

cvs.addEventListener(
  "mousemove",
  function (event) {
    cvsrect = this.getBoundingClientRect();
    ctx.clearRect(0, 0, cvsrect.width, cvsrect.height);
    mx = event.offsetX;
    my = event.offsetY;
    draw();
  },
  false
);

또, 마우스가 움직일 때마다 캔버스를 지우고 새로 그려준다. 그렇지 않으면  마우스를 가까이 하여 보여졌던 값들이 다시 사라지지 않을 수도 있고, 사라져도 지우는 과정에서 그래프가 겹친다면 같이 지워질 수 있기 때문이다. 그리고, 이 방법이 내 생각엔 가장 간단히 처리할 수 있는 방법이었다.

 

사실 별 생각없이 작성하였고, javascript는 입대한 이후로 만져본 적이 없어서(지금은 예비군입니다.) 코드가 지저분하거나 '어 이걸 왜 이렇게 하지?' 생각이 드는 코드가 있을 수도 있다. 그래서 많이 허접한 꺾은선 그래프가 만들어 졌다. 하지만 허접한 것들은 꽤 괜찮은 것들의 모체이니 언젠간 나도 쓸만한걸 만들 수 있을 것이다.

반응형

댓글