Vanilla JS로 꺾은선 그래프 만들기
데이터 트렌드와 변화를 시각적으로 효과적으로 보여주는 데 가장 적합한 그래프 중 하나가 바로 꺾은선 그래프(Line Chart)입니다. 이번 포스팅에서는 외부 라이브러리 의존성 없이, 오직 순수 자바스크립트(Vanilla JS)와 HTML5의 <canvas>
API만을 사용하여 동적인 꺾은선 그래프를 직접 구현하는 방법을 상세히 다룹니다. 데이터 포인트 계산부터 선 그리기, 축 및 라벨 추가까지 모든 과정을 직접 코딩하며 Canvas API의 강력한 기능을 체험해 보세요.
🧩 사용 기술
- HTML5
<canvas>
: 웹 페이지에 픽셀 단위의 그래픽을 그릴 수 있는 요소로, JavaScript를 통해 동적인 차트 렌더링에 활용됩니다. - 자바스크립트 기본 문법: 배열 처리, 반복문(
forEach
), 조건문 등을 사용하여 데이터를 조작하고 캔버스에 그리는 로직을 구성합니다. - 캔버스 2D 렌더링 컨텍스트:
beginPath()
,moveTo()
,lineTo()
,arc()
,stroke()
,fillText()
등 꺾은선 그래프의 선과 점, 텍스트를 그리는 데 필요한 다양한 2D 그리기 메서드를 활용합니다. - 코드 하이라이트 (Prism.js 또는 Highlight.js 권장): 본문에 포함된 HTML 및 JavaScript 코드 블록의 가독성을 높이기 위해 코드 강조 라이브러리를 활용합니다. 참고: 이 기능은 블로그 템플릿의
<head>
또는<body>
태그 내에 해당 라이브러리의 CSS 및 JavaScript 파일을 직접 추가해야만 작동합니다. 추가하지 않으면 코드에 색상이 입혀지지 않고 단순히 텍스트로 보일 수 있습니다.
💻 코드 예제
꺾은선 그래프를 그릴 기본적인 HTML 구조와 전체 JavaScript 코드를 살펴보겠습니다. 이 코드는 준비된 데이터를 기반으로 캔버스에 꺾은선 그래프를 렌더링하는 핵심 로직을 포함합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vanilla JS 꺾은선 그래프 예제</title>
</head>
<body>
<h1>간단한 꺾은선 그래프</h1>
<!-- 이 캔버스는 코드 예제 블록 안에 있는 HTML 구조를 보여주기 위한 것입니다. -->
<canvas id="lineChartCodeExample" width="600" height="400">브라우저가 캔버스를 지원하지 않습니다.</canvas>
<script>
const canvas = document.getElementById("lineChartCodeExample");
const ctx = canvas.getContext("2d");
const data = [
{ label: "1월", value: 50, color: "#4CAF50" },
{ label: "2월", value: 70, color: "#2196F3" },
{ label: "3월", value: 40, color: "#FFC107" },
{ label: "4월", value: 90, color: "#9C27B0" },
{ label: "5월", value: 60, color: "#F44336" }
];
const chartWidth = canvas.width - 100; // 좌우 여백 제외
const chartHeight = canvas.height - 80; // 상하 여백 제외
const startX = 60; // X축 시작 위치
const startY = canvas.height - 40; // Y축 시작 위치 (캔버스 하단에서 여백 위)
const maxValue = Math.max(...data.map(item => item.value));
// 축 그리기
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(startX, startY - chartHeight); // Y축
ctx.lineTo(startX + chartWidth, startY); // X축
ctx.strokeStyle = "#333";
ctx.lineWidth = 2;
ctx.stroke();
// 데이터 포인트 및 선 그리기
ctx.beginPath();
// 첫 번째 데이터 포인트로 시작점 이동
ctx.moveTo(startX + (0 / (data.length - 1)) * chartWidth, startY - (data[0].value / maxValue) * chartHeight);
data.forEach((item, index) => {
const x = startX + (index / (data.length - 1)) * chartWidth; // X 좌표 계산 (균등 간격)
const y = startY - (item.value / maxValue) * chartHeight; // Y 좌표 계산 (값에 비례)
ctx.lineTo(x, y); // 현재 점에서 다음 점까지 선을 그림
// 데이터 포인트 표시 (원)
ctx.fillStyle = item.color; // 점의 색상
ctx.beginPath(); // 새로운 경로 시작
ctx.arc(x, y, 5, 0, 2 * Math.PI); // 원 그리기 (x, y, 반지름, 시작각, 끝각)
ctx.fill(); // 원 채우기 (데이터 포인트 색상으로)
// 라벨 (X축 아래)
ctx.fillStyle = "#333";
ctx.font = "12px sans-serif";
ctx.textAlign = "center";
ctx.fillText(item.label, x, startY + 20);
// 값 (포인트 위)
ctx.fillText(item.value, x, y - 10);
});
ctx.strokeStyle = "blue"; // 꺾은선 색상
ctx.lineWidth = 2; // 꺾은선 두께
ctx.stroke(); // 꺾은선 그리기
// Y축 값 라벨 (최대값과 0)
ctx.fillStyle = "#333";
ctx.font = "12px sans-serif";
ctx.textAlign = "right";
ctx.textBaseline = "middle";
ctx.fillText(maxValue, startX - 10, startY - chartHeight);
ctx.fillText("0", startX - 10, startY);
</script>
</body>
</html>
🖼️ 결과 미리보기
위 코드의 원리를 적용하면 아래와 같이 간단한 꺾은선 그래프가 웹 페이지에 출력됩니다. 코드가 제대로 동작하는지 직접 확인해 보세요.
📌 코드 설명
HTML5 <canvas>
와 순수 JavaScript로 꺾은선 그래프를 그리는 과정은 크게 HTML 구조 준비와 JavaScript 로직 구현으로 나눌 수 있습니다. 각 부분의 주요 구성 요소와 동작 원리를 상세히 설명합니다.
1. HTML 구조
<canvas id="lineChartCodeExample" width="600" height="400">
: 웹 페이지에 꺾은선 그래프가 그려질 영역을 정의하는 캔버스 요소입니다.id
속성을 통해 JavaScript에서 이 요소를 참조할 수 있으며,width
와height
로 캔버스의 크기를 지정합니다. 이 캔버스는 코드 예시의 일부로, 실제 차트 렌더링은 '결과 미리보기' 섹션의 캔버스에서 이루어집니다.
2. JavaScript 로직
(1) 데이터 준비 및 캔버스 설정
data
배열: 차트에 표시할 각 데이터 포인트의 정보를 담고 있는 객체 배열입니다. 각 객체는label
(항목 이름),value
(값),color
(데이터 포인트 색상)를 포함합니다.- 캔버스 및 컨텍스트 가져오기:
document.getElementById()
로 캔버스 요소를 가져오고,getContext("2d")
로 2D 렌더링 컨텍스트를 얻습니다. - 차트 영역 및 축 계산: 캔버스 전체 크기에서 여백을 제외한 실제 차트가 그려질
chartWidth
,chartHeight
를 정의합니다.startX
와startY
는 X축과 Y축이 만나는 원점의 좌표를 설정합니다. maxValue
계산: 데이터 값 중 가장 큰 값을 찾아 선의 Y축 높이를 스케일링하는 기준점으로 사용합니다.Math.max()
와map()
함수를 활용합니다.
(2) 축 그리기
ctx.beginPath()
,ctx.moveTo()
,ctx.lineTo()
: 새로운 경로를 시작하고, X축과 Y축의 선을 그립니다.lineTo()
는 현재 위치에서 지정된 좌표까지 선을 그립니다.ctx.strokeStyle
&ctx.lineWidth
: 축의 색상과 두께를 설정합니다.ctx.stroke()
: 정의된 경로(여기서는 X, Y 축 선)를 그립니다.
(3) 데이터 포인트 및 선 그리기
- 선 경로 시작: 꺾은선 그래프의 선은 모든 데이터 포인트를 연결하는 하나의 경로로 그려집니다. 따라서 첫 번째 데이터 포인트의 좌표를 계산하여
ctx.moveTo()
로 선 그리기의 시작점을 설정합니다. - 데이터 반복 (
forEach
):data
배열의 각 항목에 대해 반복하며 선을 긋고 데이터 포인트를 그립니다.x
좌표 계산: 각 데이터 포인트의 X 좌표는 `startX`에서 `(index / (data.length - 1)) * chartWidth`를 더하여 계산합니다. `data.length - 1`로 나누는 이유는 N개의 데이터 포인트가 있을 때 그 사이에는 N-1개의 간격이 있기 때문입니다.y
좌표 계산: 각 데이터 포인트의 Y 좌표는 `startY`에서 해당 `value`가 `maxValue` 대비 차지하는 비율만큼 `chartHeight`를 곱한 값을 빼서 계산합니다. 캔버스의 Y축은 아래로 갈수록 값이 커지므로, 그래프를 위로 그리려면 빼는 연산이 필요합니다.ctx.lineTo(x, y)
: 이전에 설정된 시작점 또는 마지막으로 그려진 점에서 현재 데이터 포인트의 (x, y)까지 직선을 그립니다. 모든 데이터 포인트에 대해 이 작업이 반복되어 꺾은선이 형성됩니다.- 데이터 포인트 (원) 그리기: 각 데이터 포인트를 시각적으로 표시하기 위해
ctx.beginPath()
,ctx.arc()
(원을 그림),ctx.fill()
(원을 채움)을 사용합니다. **주의: 각 원을 그릴 때는 꺾은선 경로와 독립적으로 `ctx.beginPath()`를 호출하여 새로운 경로로 시작해야 합니다.** 그렇지 않으면 꺾은선 경로에 원 그리기 명령이 포함되어 그래프 모양이 왜곡될 수 있습니다.
ctx.strokeStyle
&ctx.lineWidth
: 꺾은선의 색상과 두께를 설정합니다.ctx.stroke()
: 정의된 꺾은선 경로를 화면에 그립니다.
(4) 라벨 및 값 추가
ctx.fillText()
: 각 데이터 포인트 아래에 X축 라벨(항목 이름)을, 그리고 각 포인트 위에 해당 값(숫자)을 그립니다.ctx.font
,ctx.textAlign
,ctx.textBaseline
: 텍스트의 폰트, 수평 정렬, 수직 정렬을 설정하여 라벨과 값이 보기 좋게 표시되도록 합니다.- Y축 값 라벨:
maxValue
와 '0'을 Y축 옆에 표시하여 차트의 스케일을 직관적으로 보여줍니다.
3. 사용된 주요 Canvas 메서드 및 속성
ctx.getContext("2d")
: 캔버스에 2D 그래픽을 그리기 위한 렌더링 컨텍스트를 반환합니다.ctx.beginPath()
: 새로운 경로를 시작합니다. 독립적인 도형이나 선을 그릴 때마다 호출합니다.ctx.moveTo(x, y)
: 현재 드로잉 위치를 (x, y)로 이동시킵니다. 선 그리기의 시작점을 설정할 때 사용됩니다.ctx.lineTo(x, y)
: 현재 드로잉 위치에서 (x, y)까지 직선을 그립니다. 꺾은선이나 축을 그릴 때 사용됩니다.ctx.arc(x, y, radius, startAngle, endAngle)
: (x, y)를 중심으로 하는 원호를 그립니다. 데이터 포인트를 원으로 표시할 때 사용됩니다.ctx.stroke()
: 현재 경로를 따라 선을 그립니다 (윤곽선).ctx.fill()
: 현재 경로로 정의된 영역을 채웁니다.ctx.fillText(text, x, y)
: 지정된 좌표에 텍스트를 그립니다. 라벨과 값을 표시할 때 사용됩니다.ctx.strokeStyle
/ctx.fillStyle
: 선을 그리거나 도형을 채울 때 사용할 색상을 설정합니다.ctx.lineWidth
: 선의 두께를 설정합니다.ctx.font
: 텍스트의 폰트 스타일, 크기, 서체를 설정합니다.ctx.textAlign
/ctx.textBaseline
: 텍스트의 수평 및 수직 정렬 방식을 설정합니다.
4. 좌표 계산의 중요성
- 캔버스 좌표계 이해: 캔버스의 (0,0)은 좌측 상단입니다. Y축은 아래로 갈수록 값이 증가합니다. 따라서 그래프를 바닥부터 위로 그리거나, 값이 커질수록 위로 가도록 하려면 Y 좌표 계산에 유의해야 합니다.
- 스케일링: 데이터의 값 범위(최소값~최대값)가 캔버스 높이에 맞춰 잘 표시될 수 있도록
maxValue
를 기준으로 높이를 스케일링하는 것이 중요합니다. - 여백 및 간격: 차트가 캔버스 가장자리에 붙지 않고, 데이터 포인트들이 적절한 간격을 유지하도록 여백(
startX
,startY
)과 X축 간격(index / (data.length - 1) * chartWidth
)을 잘 계산해야 합니다.
🚀 응용 팁
위 예제를 기반으로 꺾은선 그래프를 더욱 다채롭고 기능적으로 만들 수 있는 몇 가지 응용 팁입니다:
- 다중 꺾은선 그래프: 여러 개의 데이터셋을 사용하여 하나의 차트에 여러 개의 꺾은선을 그려 비교 분석할 수 있습니다. 각 선마다 다른 색상과 스타일을 적용합니다.
- 영역 채우기: 꺾은선 아래 영역을 색상으로 채워 시각적인 강조 효과를 줄 수 있습니다 (Area Chart).
lineTo()
로 선을 그린 후 X축으로 내려와fill()
을 사용합니다. - 마우스 호버(hover) 시 인터랙션: 마우스가 데이터 포인트 위에 올라갔을 때 해당 포인트의 자세한 정보(툴팁)를 표시하거나 강조 효과를 줄 수 있습니다.
- 애니메이션 효과 추가: 차트가 로드될 때 선이 그려지는 애니메이션이나, 데이터가 변경될 때 부드럽게 전환되는 애니메이션을
requestAnimationFrame
을 사용하여 구현할 수 있습니다. - 동적 데이터 업데이트: 외부 API나 사용자 입력으로부터 데이터를 받아와 그래프를 실시간으로 업데이트하는 기능을 추가할 수 있습니다.
- 눈금 및 그리드 라인: X, Y축에 더 상세한 눈금(tick)과 그리드 라인(Grid Line)을 추가하여 데이터 값을 더 정확하게 읽을 수 있도록 돕습니다.
- 반응형 디자인: 윈도우 크기 변화에 따라 캔버스 크기와 그래프 요소들을 동적으로 조정하여 모바일 환경에서도 최적화된 뷰를 제공합니다.
🛠️ 주의사항 및 개선 포인트
순수 JavaScript와 Canvas API를 사용하여 꺾은선 그래프를 구현할 때 고려해야 할 몇 가지 주의사항과 개선할 수 있는 부분들입니다.
- 복잡도 관리: 그래프의 기능이 복잡해질수록 코드의 복잡도도 증가합니다. 모듈화, 함수 분리 등을 통해 코드를 관리하기 쉽게 만드는 것이 중요합니다.
- 데이터 스케일링 정교화: 데이터의 최소값이 0이 아닐 경우, Y축 스케일을 최소값부터 최대값까지 동적으로 조정하는 로직이 필요합니다.
- 성능 최적화: 데이터 포인트가 매우 많아지거나 복잡한 애니메이션을 적용할 경우, 캔버스 렌더링 성능이 저하될 수 있습니다. 불필요한 재그리기를 피하고, 필요한 부분만 업데이트하는 기법을 고려해야 합니다.
- 접근성 (Accessibility): 캔버스에 그려진 시각적 정보는 시각 장애인에게 접근하기 어렵습니다. 그래프 데이터를 HTML 테이블 형태로 별도 제공하거나, ARIA 속성을 활용하여 웹 접근성을 높이는 것을 고려해야 합니다.
댓글 없음:
댓글 쓰기