Learning
레슨 7 / 8·25분

실전: 대시보드 만들기

실전 프로젝트: 매출 분석 대시보드

지금까지 배운 Chart.js 기능을 조합하여 실제 매출 분석 대시보드를 만들어 봅니다. 여러 차트를 하나의 페이지에 배치하고, 필터링과 실시간 업데이트 기능을 구현합니다.

1단계: HTML 레이아웃 구성

html
<div class="dashboard">
  <h1>매출 분석 대시보드</h1>

  <!-- 필터 영역 -->
  <div class="filters">
    <select id="yearFilter">
      <option value="2024">2024년</option>
      <option value="2023">2023년</option>
    </select>
    <select id="regionFilter">
      <option value="all">전체 지역</option>
      <option value="seoul">서울</option>
      <option value="busan">부산</option>
    </select>
  </div>

  <!-- 차트 그리드 -->
  <div class="chart-grid">
    <div class="chart-card">
      <canvas id="revenueChart"></canvas>
    </div>
    <div class="chart-card">
      <canvas id="categoryChart"></canvas>
    </div>
    <div class="chart-card">
      <canvas id="trendChart"></canvas>
    </div>
    <div class="chart-card">
      <canvas id="performanceChart"></canvas>
    </div>
  </div>
</div>

2단계: 데이터 모델 정의

javascript
// 샘플 데이터
var salesData = {
  months: ['1월', '2월', '3월', '4월', '5월', '6월',
           '7월', '8월', '9월', '10월', '11월', '12월'],
  revenue: [120, 190, 150, 250, 220, 300, 280, 310, 260, 340, 290, 380],
  costs:   [80, 120, 100, 160, 140, 180, 170, 190, 160, 210, 180, 230],
  categories: {
    labels: ['전자제품', '의류', '식품', '도서', '기타'],
    values: [35, 25, 20, 12, 8],
  },
  performance: {
    labels: ['매출 성장률', '고객 만족도', '재구매율', '배송 속도', '품질 평가'],
    current: [85, 90, 75, 80, 88],
    target:  [90, 85, 80, 90, 85],
  },
};

3단계: 매출/비용 복합 차트

javascript
// 매출 vs 비용 복합 차트
var revenueCtx = document.getElementById('revenueChart').getContext('2d');

var revenueGradient = revenueCtx.createLinearGradient(0, 0, 0, 300);
revenueGradient.addColorStop(0, 'rgba(54, 162, 235, 0.4)');
revenueGradient.addColorStop(1, 'rgba(54, 162, 235, 0.0)');

var revenueChart = new Chart(revenueCtx, {
  type: 'bar',
  data: {
    labels: salesData.months,
    datasets: [
      {
        type: 'bar',
        label: '매출',
        data: salesData.revenue,
        backgroundColor: 'rgba(54, 162, 235, 0.6)',
        borderRadius: 4,
        order: 2,
      },
      {
        type: 'bar',
        label: '비용',
        data: salesData.costs,
        backgroundColor: 'rgba(255, 159, 64, 0.6)',
        borderRadius: 4,
        order: 3,
      },
      {
        type: 'line',
        label: '순이익',
        data: salesData.revenue.map(function(r, i) {
          return r - salesData.costs[i];
        }),
        borderColor: 'rgb(75, 192, 192)',
        backgroundColor: revenueGradient,
        fill: true,
        tension: 0.3,
        order: 1,
      },
    ],
  },
  options: {
    responsive: true,
    plugins: {
      title: {
        display: true,
        text: '월별 매출/비용/순이익',
        font: { size: 16 },
      },
    },
    scales: {
      y: {
        beginAtZero: true,
        ticks: {
          callback: function(v) { return v + '만원'; },
        },
      },
    },
  },
});

4단계: 카테고리 도넛 차트

javascript
// 카테고리별 매출 비중
var categoryCtx = document.getElementById('categoryChart').getContext('2d');

var categoryChart = new Chart(categoryCtx, {
  type: 'doughnut',
  data: {
    labels: salesData.categories.labels,
    datasets: [{
      data: salesData.categories.values,
      backgroundColor: [
        'rgba(54, 162, 235, 0.7)',
        'rgba(255, 99, 132, 0.7)',
        'rgba(75, 192, 192, 0.7)',
        'rgba(255, 205, 86, 0.7)',
        'rgba(153, 102, 255, 0.7)',
      ],
      hoverOffset: 10,
    }],
  },
  options: {
    responsive: true,
    cutout: '55%',
    plugins: {
      title: {
        display: true,
        text: '카테고리별 매출 비중',
        font: { size: 16 },
      },
      legend: { position: 'bottom' },
    },
  },
});

5단계: 필터 연동

javascript
// 필터 변경 시 차트 업데이트
document.getElementById('yearFilter').addEventListener('change', function(e) {
  var year = e.target.value;

  // API에서 데이터 가져오기 (여기서는 시뮬레이션)
  var newData = fetchDataByYear(year);

  // 차트 데이터 업데이트
  revenueChart.data.datasets[0].data = newData.revenue;
  revenueChart.data.datasets[1].data = newData.costs;
  revenueChart.data.datasets[2].data = newData.revenue.map(function(r, i) {
    return r - newData.costs[i];
  });
  revenueChart.update();

  categoryChart.data.datasets[0].data = newData.categories;
  categoryChart.update();
});

function fetchDataByYear(year) {
  // 실제로는 API 호출
  return {
    revenue: salesData.revenue.map(function(v) {
      return Math.round(v * (year === '2023' ? 0.85 : 1));
    }),
    costs: salesData.costs.map(function(v) {
      return Math.round(v * (year === '2023' ? 0.9 : 1));
    }),
    categories: salesData.categories.values,
  };
}
💡

대시보드를 구성할 때는 반응형 레이아웃(CSS Grid나 Flexbox)과 함께 Chart.js의 responsive: true, maintainAspectRatio: false 옵션을 활용하세요. 다양한 화면 크기에서 차트가 올바르게 표시됩니다.