교통사고 데이터(TAAS)로 배우는 9가지 차트 + ⭐인터랙티브 필터 · 코드 그대로 복사 → 데이터만 교체
type과 data만 바꿔 쓰면 됩니다.
<head>에 한 번만<canvas id="..."> 하나가 차트 한 개fetch()로 외부 파일 로드new Chart(canvas, { type, data, options })<!-- ① Chart.js CDN 로드 (HTML head에 한 번만) --> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> <!-- ② 차트가 그려질 캔버스 (id는 자유) --> <canvas id="myChart"></canvas> <script> // ③ 데이터 준비 — A안: 배열로 직접 작성 const labels = ['2020','2021','2022','2023']; const values = [100, 120, 95, 140]; // ③ 데이터 준비 — B안: 외부 JSON/CSV에서 fetch (실무 패턴) // fetch('data.json') // .then(res => res.json()) // JSON 파싱 // .then(rows => { // rows = [{year:2020,count:100}, ...] // const labels = rows.map(r => r.year); // const values = rows.map(r => r.count); // drawChart(labels, values); // }); // ④ Chart 인스턴스 생성 new Chart(document.getElementById('myChart'), { type: 'bar', // 차트 종류만 바꾸면 다른 차트 data: { labels, // x축 라벨 datasets: [{ label: '판매량', data: values, // 값 배열 backgroundColor: 'rgba(59,130,246,0.7)' }] }, options: { responsive: true, // 부모 너비에 자동 맞춤 maintainAspectRatio: false // 부모 div 높이 사용 → 캔버스를 div로 감싸기 } }); </script>
type:'bar'를 'line', 'pie', 'radar', 'scatter', 'bubble', 'polarArea', 'doughnut'로 바꾸면 그게 바로 그 차트입니다. 데이터 형식만 차트별로 살짝 다릅니다(아래 탭에서 하나씩).fetch().then(r=>r.json()), ⓑ CSV는 fetch().then(r=>r.text()) 후 직접 split 또는 PapaParse 라이브러리, ⓒ 구글 시트 발행 URL을 fetch. 어느 쪽이든 최종적으로 labels 배열과 values 배열을 만든다는 점은 동일합니다.indexAxis:'y')나 트리맵을 고려.labels · values 두 배열만 자기 값으로 교체. 길이만 같으면 됩니다.// ─── ① CDN: Chart.js 한 줄 (head에 한 번) ─── // <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> // ─── ② 캔버스: <canvas id="barChart"></canvas> ─── // ③ 데이터 준비 ─── 여기만 바꾸세요 const labels = [2016,2017,2018,2019,2020,2021,2022,2023]; const values = [220917,216335,217148,229600,209654,203130,196836,191418]; // ④ 차트 생성 new Chart(document.getElementById('barChart'), { type: 'bar', data: { labels, datasets: [{ label: '교통사고 건수', data: values, backgroundColor: 'rgba(59,130,246,0.7)', borderColor: 'rgb(59,130,246)', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: '연도별 교통사고 건수' } }, scales: { y: { ticks: { callback: v => v.toLocaleString() } } } } });
maintainAspectRatio:false + 높이가 정해진 부모 div 조합이 핵심. 안 쓰면 차트가 무한히 커집니다.yAxisID를 다르게 주고, scales에서 두 축(y, y2)을 따로 정의.// ② 캔버스: <canvas id="lineChart"></canvas> // ③ 데이터 준비 ─── 여기만 바꾸세요 const years = [2016,2017,2018,2019,2020,2021,2022,2023]; const accidents = [22.1,21.6,21.7,23.0,21.0,20.3,19.7,19.1]; // 만건 const deaths = [4292,4185,3781,3349,3081,2916,2735,2551]; // 명 // ④ 차트 생성 ─── 단위 다른 두 시리즈는 yAxisID로 축 분리 new Chart(document.getElementById('lineChart'), { type: 'line', data: { labels: years, datasets: [ { label: '사고건수(만건)', data: accidents, yAxisID: 'y', borderColor: '#3b82f6', backgroundColor: 'rgba(59,130,246,0.15)', tension: 0.3, fill: true, pointRadius: 4 }, { label: '사망자(명)', data: deaths, yAxisID: 'y2', borderColor: '#ef4444', backgroundColor: 'rgba(239,68,68,0.15)', tension: 0.3, fill: true, pointRadius: 4 } ]}, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, // 같은 x에서 두 점 동시 툴팁 scales: { y: { position: 'left', title: { display: true, text: '사고건수(만)' } }, y2: { position: 'right', title: { display: true, text: '사망자(명)' }, grid: { drawOnChartArea: false } } // 오른쪽 축 격자 숨김 } } });
type만 'pie' ↔ 'doughnut'으로 바꾸면 끝. 도넛은 가운데에 합계 텍스트 넣기 좋음.labels와 values 두 배열만 길이 맞춰 교체. 색은 자동 처리하거나 직접 지정.// ② 캔버스 2개: <canvas id="pieChart">, <canvas id="doughnutChart"> // ③ 데이터 준비 const labels = ['차대차','차대사람','차량단독','철길건널목']; const values = [142350, 42580, 6380, 108]; const colors = ['#3b82f6','#ef4444','#f59e0b','#10b981']; const total = values.reduce((a,b) => a+b, 0); // ④ 차트 생성 ─── pie/doughnut은 type만 다르므로 함수로 묶음 function drawPie(canvasId, type) { new Chart(document.getElementById(canvasId), { type, // 'pie' 또는 'doughnut' data: { labels, datasets: [{ data: values, backgroundColor: colors, borderColor: 'white', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { tooltip: { callbacks: { label: c => ` ${c.label}: ${c.raw.toLocaleString()}건 ` + `(${(c.raw/total*100).toFixed(1)}%)` }}} } }); } drawPie('pieChart', 'pie'); drawPie('doughnutChart', 'doughnut');
labels = 꼭짓점 항목, datasets = 비교할 대상들(다각형 N개).// ② <canvas id="radarChart"></canvas> // ③ 데이터 준비 ─── labels는 항목, 각 dataset은 비교 대상 const labels = ['서울','경기','부산','대구','인천','광주','대전']; const accidents = [342, 584, 156, 98, 145, 72, 81]; // 100건 단위 const deaths = [252, 681, 178, 98, 141, 67, 83]; // 명 // ④ 차트 생성 new Chart(document.getElementById('radarChart'), { type: 'radar', data: { labels, datasets: [ { label: '사고건수(백건)', data: accidents, borderColor: '#3b82f6', backgroundColor: 'rgba(59,130,246,0.25)', pointBackgroundColor: '#3b82f6' }, { label: '사망자(명)', data: deaths, borderColor: '#ef4444', backgroundColor: 'rgba(239,68,68,0.25)', pointBackgroundColor: '#ef4444' } ]}, options: { responsive: true, maintainAspectRatio: false } });
√값으로 변환 권장. 직접 r=값으로 하면 큰 값이 과장됨.{x, y, r} 객체. 시도별로 색을 다르게 주려면 dataset을 시도별로 분리.// ② <canvas id="bubbleChart"></canvas> // ③ 데이터 준비 ─── 시도별 (x: 사고건수, y: 사망자, r: 인구만) const sido = ['서울','경기','부산','대구','인천','광주','대전']; const accidents = [34215,58432,15621,9843,14523,7234,8123]; const deaths = [252,681,178,98,141,67,83]; const population = [950,1340,330,240,295,143,145]; // 만 명 // 시도별로 색을 분리하기 위해 dataset 7개 생성 const datasets = sido.map((name, i) => ({ label: name, data: [{ x: accidents[i], y: deaths[i], r: Math.sqrt(population[i]) * 0.8 // 면적이 인구에 비례 }], backgroundColor: `hsla(${i*45},70%,55%,0.65)`, borderColor: `hsla(${i*45},70%,40%,0.9)`, borderWidth: 1 })); // ④ 차트 생성 new Chart(document.getElementById('bubbleChart'), { type: 'bubble', data: { datasets }, options: { responsive: true, maintainAspectRatio: false, plugins: { tooltip: { callbacks: { label: c => `${c.dataset.label}: 사고 ${c.raw.x.toLocaleString()}건, ` + `사망 ${c.raw.y}명` }}}, scales: { x: { title: { display: true, text: '사고건수' } }, y: { title: { display: true, text: '사망자(명)' } } } } });
{x, y} 객체 배열. label을 추가하면 툴팁에서 점 이름 표시 가능.// ② <canvas id="scatterChart"></canvas> // ③ 데이터 준비 ─── {x, y, label} 객체 배열 const points = [ { x: 34215, y: 0.74, label: '서울' }, { x: 58432, y: 1.17, label: '경기' }, { x: 15621, y: 1.14, label: '부산' }, { x: 9843, y: 1.00, label: '대구' }, { x: 14523, y: 0.97, label: '인천' }, { x: 7234, y: 0.93, label: '광주' }, { x: 8123, y: 1.02, label: '대전' } ]; // ④ 차트 생성 new Chart(document.getElementById('scatterChart'), { type: 'scatter', data: { datasets: [{ label: '시도', data: points, backgroundColor: 'rgba(99,102,241,0.7)', borderColor: '#4338ca', pointRadius: 8, pointHoverRadius: 11 }]}, options: { responsive: true, maintainAspectRatio: false, plugins: { tooltip: { callbacks: { label: c => `${c.raw.label}: ${c.raw.x.toLocaleString()}건, ${c.raw.y}%` }}}, scales: { x: { title: { display: true, text: '사고건수' } }, y: { title: { display: true, text: '사망률(%)' } } } } });
type만 'polarArea'.// ② <canvas id="polarChart"></canvas> // ③ 데이터 준비 const labels = ['주중 주간','주중 야간','주말 주간','주말 야간','심야(0–6시)']; const values = [78532, 42180, 38420, 28910, 9376]; const colors = ['rgba(59,130,246,0.6)', 'rgba(239,68,68,0.6)', 'rgba(245,158,11,0.6)', 'rgba(16,185,129,0.6)', 'rgba(139,92,246,0.6)']; // ④ 차트 생성 ─── pie와 거의 동일 new Chart(document.getElementById('polarChart'), { type: 'polarArea', data: { labels, datasets: [{ data: values, backgroundColor: colors }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: '시간대별 사고 분포' } } } });
type을 다르게 주면 자동으로 합쳐짐. 이중축과 결합하면 강력.type은 기본값(보통 'bar'), 각 dataset에서 type을 덮어써서 섞기.// ② <canvas id="mixedChart"></canvas> // ③ 데이터 준비 const years = [2016,2017,2018,2019,2020,2021,2022,2023]; const counts = [220917,216335,217148,229600,209654,203130,196836,191418]; const deaths = [4292,4185,3781,3349,3081,2916,2735,2551]; const fatality = counts.map((c,i) => +(deaths[i]/c*100).toFixed(2)); // 사망률(%) // ④ 차트 생성 ─── dataset마다 type을 따로 지정 new Chart(document.getElementById('mixedChart'), { type: 'bar', // 기본값(아래에서 덮어쓸 수 있음) data: { labels: years, datasets: [ { type: 'bar', label: '사고건수', data: counts, yAxisID: 'y', backgroundColor: 'rgba(59,130,246,0.6)' }, { type: 'line', label: '사망률(%)', data: fatality, yAxisID: 'y2', borderColor: '#ef4444', tension: 0.3, pointRadius: 5, fill: false } ]}, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, scales: { y: { position: 'left', title: { display: true, text: '건수' }, ticks: { callback: v => v.toLocaleString() } }, y2: { position: 'right', title: { display: true, text: '사망률(%)' }, grid: { drawOnChartArea: false } } } } });
key=면적 기준 컬럼, groups=계층 컬럼들.// ① 트리맵은 별도 플러그인 CDN을 추가로 로드 // <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> // <script src="https://cdn.jsdelivr.net/npm/chartjs-chart-treemap@2.3.1/dist/chartjs-chart-treemap.min.js"></script> // ② <canvas id="treemapChart"></canvas> // ③ 데이터 준비 ─── 객체 배열(rows). 각 row가 한 박스 const tree = [ { 시도:'서울', 유형:'차대차', 건수:25800 }, { 시도:'서울', 유형:'차대사람', 건수: 6800 }, { 시도:'경기', 유형:'차대차', 건수:44100 }, { 시도:'경기', 유형:'차대사람', 건수:10800 }, { 시도:'부산', 유형:'차대차', 건수:11800 }, { 시도:'인천', 유형:'차대차', 건수:11000 } // ... 자기 데이터로 교체 ]; const color = { 서울:'#3b82f6', 경기:'#10b981', 부산:'#f59e0b', 대구:'#a855f7', 인천:'#ef4444' }; // ④ 차트 생성 new Chart(document.getElementById('treemapChart'), { type: 'treemap', data: { datasets: [{ tree, // 위 객체 배열 key: '건수', // 면적 기준 컬럼 groups: ['시도', '유형'], // 계층 (큰 그룹 → 작은 그룹) spacing: 1, borderWidth: 1, borderColor: 'rgba(255,255,255,0.6)', backgroundColor(c) { if (c.type !== 'data') return 'transparent'; const sido = c.raw._data.시도 || c.raw.g; return (color[sido] || '#94a3b8') + (c.raw.l === 0 ? 'cc' : '88'); }, labels: { display: true, color: 'white', font: { size: 11, weight: '600' }, formatter: c => c.raw ? [c.raw.g, c.raw.v.toLocaleString()] : '' } }]}, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { callbacks: { title: items => items[0].raw.g, label: c => `건수: ${c.raw.v.toLocaleString()}` }} } } });
chart.data.datasets[0].data = newData; chart.update(); (애니메이션 자연스러움)chart.destroy() → new Chart() 재생성 (Chart.js v4의 정석)destroy() + 재생성이 안전. 두 패턴을 섞지 말고 통일.// ② 캔버스 + 컨트롤 UI // <select id="metric-select">...</select> // <select id="type-select">...</select> // <input id="range-input" type="range" min="2" max="8" value="8"> // <canvas id="filterChart"></canvas> // ③ 데이터 준비 ─── 지표를 객체로 묶어 두면 select 값으로 바로 꺼냄 const years = [2016,2017,2018,2019,2020,2021,2022,2023]; const dataMap = { 사고건수: [220917,216335,217148,229600,209654,203130,196836,191418], 사망자수: [4292,4185,3781,3349,3081,2916,2735,2551], 부상자수: [331720,322780,323037,341712,304237,291608,273363,260296] }; const color = { 사고건수: 'rgba(59,130,246,0.7)', 사망자수: 'rgba(239,68,68,0.7)', 부상자수: 'rgba(245,158,11,0.7)' }; // ④ 차트 생성 ─── render() 함수로 묶어 컨트롤 변경 시마다 호출 let chart = null; function render() { // 4-1. 컨트롤에서 현재 값 읽기 const metric = document.getElementById('metric-select').value; const type = document.getElementById('type-select').value; const count = +document.getElementById('range-input').value; // 4-2. 데이터를 잘라서 준비 const labels = years.slice(-count); const values = dataMap[metric].slice(-count); // 4-3. type이 바뀔 수 있으니 항상 destroy → 재생성 (패턴 B) if (chart) chart.destroy(); chart = new Chart(document.getElementById('filterChart'), { type, data: { labels, datasets: [{ label: metric, data: values, backgroundColor: color[metric], borderColor: color[metric], borderWidth: 2, fill: type=='line'||type=='radar', tension: 0.3 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: `최근 ${count}년 ${metric}` } } } }); } // 4-4. 모든 컨트롤에 이벤트 연결 ['metric-select', 'type-select', 'range-input'].forEach(id => document.getElementById(id).addEventListener('change', render)); document.getElementById('range-input').addEventListener('input', render); render(); // 첫 진입 시 1회 호출