Learning
레슨 5 / 8·20분

애니메이션과 인터랙션

애니메이션 설정

Chart.js는 차트가 처음 렌더링될 때와 데이터가 업데이트될 때 자동으로 애니메이션을 적용합니다. animation 옵션으로 지속 시간, 이징 함수, 콜백 등을 세밀하게 제어할 수 있습니다.

javascript
const chart = new Chart(ctx, {
  type: 'bar',
  data: { /* ... */ },
  options: {
    animation: {
      duration: 2000,
      easing: 'easeOutQuart',

      // 애니메이션 완료 시 콜백
      onComplete: function(animation) {
        console.log('애니메이션 완료!');
      },

      // 각 프레임마다 호출
      onProgress: function(animation) {
        var progress = animation.currentStep / animation.numSteps;
        console.log('진행률: ' + Math.round(progress * 100) + '%');
      },
    },

    // 데이터셋별 애니메이션 설정
    transitions: {
      active: {
        animation: { duration: 300 },
      },
    },
  },
});

이징 함수 종류

  • linear — 일정한 속도
  • easeInQuad — 느리게 시작
  • easeOutQuad — 느리게 끝남
  • easeInOutQuad — 느리게 시작하고 느리게 끝남
  • easeOutBounce — 바운스 효과
  • easeOutElastic — 탄성 효과
  • easeOutQuart — 부드럽게 감속 (추천)
  • easeInOutCubic — 자연스러운 시작과 끝

개별 속성 애니메이션

특정 속성에 대해서만 별도의 애니메이션을 적용할 수 있습니다. 예를 들어 x축은 빠르게, y축은 느리게 애니메이션하거나, 색상 변화에 별도 타이밍을 줄 수 있습니다.

javascript
options: {
  animations: {
    // Y축 값 애니메이션
    y: {
      duration: 1500,
      easing: 'easeOutBounce',
      from: function(ctx) {
        // 초기 로드 시 아래에서 시작
        if (ctx.type === 'data' && ctx.mode === 'default') {
          return ctx.chart.scales.y.getPixelForValue(0);
        }
      },
    },
    // 색상 애니메이션
    backgroundColor: {
      duration: 500,
      type: 'color',
      from: 'rgba(0, 0, 0, 0)',
    },
  },
}

툴팁 커스터마이징

javascript
options: {
  plugins: {
    tooltip: {
      enabled: true,
      mode: 'index',           // 같은 X축 인덱스의 모든 데이터 표시
      intersect: false,        // 마우스가 정확히 포인트 위가 아니어도 표시
      backgroundColor: 'rgba(0, 0, 0, 0.85)',
      titleFont: { size: 15, weight: 'bold' },
      bodyFont: { size: 13 },
      padding: 12,
      cornerRadius: 8,
      displayColors: true,     // 색상 박스 표시

      // 제목 커스터마이징
      callbacks: {
        title: function(tooltipItems) {
          return tooltipItems[0].label + ' 실적';
        },
        // 본문 커스터마이징
        label: function(context) {
          var label = context.dataset.label || '';
          var value = context.parsed.y;
          return label + ': ' + value.toLocaleString() + '원';
        },
        // 푸터 추가
        footer: function(tooltipItems) {
          var total = 0;
          tooltipItems.forEach(function(item) {
            total += item.parsed.y;
          });
          return '합계: ' + total.toLocaleString() + '원';
        },
      },
    },
  },
}

클릭 이벤트와 데이터 인터랙션

javascript
// 차트 요소 클릭 이벤트
const chart = new Chart(ctx, {
  type: 'bar',
  data: { /* ... */ },
  options: {
    onClick: function(event, elements, chart) {
      if (elements.length > 0) {
        var element = elements[0];
        var datasetIndex = element.datasetIndex;
        var index = element.index;
        var label = chart.data.labels[index];
        var value = chart.data.datasets[datasetIndex].data[index];

        console.log('클릭한 항목: ' + label + ', 값: ' + value);

        // 클릭한 막대 강조 표시
        var dataset = chart.data.datasets[datasetIndex];
        var colors = dataset.backgroundColor.map(function(color, i) {
          return i === index ? 'rgba(255, 99, 132, 0.8)' : 'rgba(54, 162, 235, 0.3)';
        });
        dataset.backgroundColor = colors;
        chart.update();
      }
    },

    onHover: function(event, elements) {
      var target = event.native ? event.native.target : event.target;
      target.style.cursor = elements.length > 0 ? 'pointer' : 'default';
    },
  },
});

실시간 데이터 업데이트

javascript
// 실시간 데이터 추가
function addData(chart, label, data) {
  chart.data.labels.push(label);
  chart.data.datasets.forEach(function(dataset, i) {
    dataset.data.push(data[i]);
  });

  // 최대 10개 데이터만 유지
  if (chart.data.labels.length > 10) {
    chart.data.labels.shift();
    chart.data.datasets.forEach(function(dataset) {
      dataset.data.shift();
    });
  }

  chart.update('none');  // 애니메이션 없이 업데이트
}

// 2초마다 데이터 추가
setInterval(function() {
  var now = new Date();
  var label = now.getHours() + ':' + String(now.getMinutes()).padStart(2, '0');
  var value = Math.floor(Math.random() * 100);
  addData(chart, label, [value]);
}, 2000);
💡

chart.update("none")을 호출하면 애니메이션 없이 즉시 차트를 갱신합니다. 실시간 데이터 스트리밍처럼 빈번한 업데이트에는 "none" 모드를 사용하여 성능을 최적화하세요.