Learning
레슨 4 / 8·20분

트랜지션과 애니메이션

D3 트랜지션 심화

D3의 트랜지션 시스템은 데이터 변화를 시각적으로 부드럽게 전달하는 핵심 도구입니다. 단순히 속성을 변경하는 것이 아니라, 시간에 따라 점진적으로 보간(interpolation)하여 자연스러운 애니메이션을 만듭니다.

기본 트랜지션 패턴

javascript
// 기본 트랜지션: 색상과 크기 변화
d3.select('#myCircle')
  .transition()
  .duration(1000)          // 1초 동안
  .ease(d3.easeElasticOut) // 탄성 이징
  .attr('r', 50)           // 반지름 50으로
  .attr('fill', '#e74c3c') // 빨간색으로
  .attr('cx', 300);        // 오른쪽으로 이동

// 트랜지션 체이닝: 순차 애니메이션
d3.select('#myRect')
  .transition()
  .duration(500)
  .attr('width', 200)
  .attr('fill', '#3498db')
  .transition()             // 첫 번째 트랜지션 후 실행
  .duration(500)
  .attr('height', 100)
  .attr('fill', '#2ecc71')
  .transition()
  .duration(300)
  .attr('opacity', 0.7);

이징 함수 (Easing Functions)

이징 함수는 애니메이션의 가속/감속 패턴을 결정합니다. D3는 다양한 내장 이징 함수를 제공하며, 커스텀 이징도 만들 수 있습니다.

  • d3.easeLinear — 일정한 속도 (기본)
  • d3.easeCubicInOut — 부드러운 시작과 끝 (가장 자연스러움)
  • d3.easeBounceOut — 바운스 효과 (바닥에 튕기는 느낌)
  • d3.easeElasticOut — 탄성 효과 (스프링처럼)
  • d3.easeBackOut — 살짝 넘어갔다 돌아오는 효과
  • d3.easeCircleInOut — 원형 가감속
  • d3.easeQuadIn — 점점 빨라지는 효과
  • d3.easeExpOut — 급격히 감속하는 효과

데이터 업데이트 애니메이션

D3의 가장 강력한 패턴 중 하나는 데이터가 변경될 때 enter/update/exit를 트랜지션과 결합하는 것입니다. 새 데이터는 등장 애니메이션으로, 기존 데이터는 위치/크기 전환으로, 삭제된 데이터는 사라지는 애니메이션으로 처리합니다.

javascript
function updateChart(newData) {
  var bars = svg.selectAll('rect')
    .data(newData, function(d) { return d.id; });  // key 함수로 데이터 매칭

  // EXIT: 삭제되는 요소 — 페이드아웃 후 제거
  bars.exit()
    .transition()
    .duration(300)
    .attr('opacity', 0)
    .attr('width', 0)
    .remove();

  // ENTER: 새로 추가되는 요소 — 아래에서 올라옴
  var enter = bars.enter()
    .append('rect')
    .attr('x', function(d) { return xScale(d.label); })
    .attr('y', innerHeight)
    .attr('width', xScale.bandwidth())
    .attr('height', 0)
    .attr('fill', '#6366f1')
    .attr('opacity', 0);

  // ENTER + UPDATE: 모든 요소에 트랜지션 적용
  enter.merge(bars)
    .transition()
    .duration(800)
    .delay(function(d, i) { return i * 50; })
    .ease(d3.easeCubicOut)
    .attr('x', function(d) { return xScale(d.label); })
    .attr('y', function(d) { return yScale(d.value); })
    .attr('width', xScale.bandwidth())
    .attr('height', function(d) { return innerHeight - yScale(d.value); })
    .attr('opacity', 1);
}

인터랙티브 애니메이션

javascript
// 마우스 호버 시 강조 효과
svg.selectAll('rect')
  .on('mouseover', function(event, d) {
    d3.select(this)
      .transition()
      .duration(200)
      .attr('fill', '#f59e0b')
      .attr('transform', 'scale(1.05)');

    // 툴팁 표시
    tooltip
      .style('opacity', 1)
      .html(d.label + ': ' + d.value)
      .style('left', (event.pageX + 10) + 'px')
      .style('top', (event.pageY - 20) + 'px');
  })
  .on('mouseout', function() {
    d3.select(this)
      .transition()
      .duration(300)
      .attr('fill', '#6366f1')
      .attr('transform', 'scale(1)');

    tooltip.style('opacity', 0);
  });

// 클릭 시 줌 인/아웃
svg.selectAll('circle')
  .on('click', function(event, d) {
    var isZoomed = d3.select(this).classed('zoomed');

    d3.select(this)
      .classed('zoomed', !isZoomed)
      .transition()
      .duration(500)
      .ease(d3.easeBackOut)
      .attr('r', isZoomed ? 5 : 15)
      .attr('stroke-width', isZoomed ? 1 : 3);
  });

커스텀 보간(Interpolation)

javascript
// 숫자 보간
d3.select('#counter')
  .transition()
  .duration(2000)
  .tween('text', function() {
    var self = this;
    var i = d3.interpolateNumber(0, 1000);
    return function(t) {
      self.textContent = Math.round(i(t)).toLocaleString();
    };
  });

// 색상 보간
var colorInterp = d3.interpolateRgb('#3498db', '#e74c3c');
console.log(colorInterp(0));   // rgb(52, 152, 219) — 시작
console.log(colorInterp(0.5)); // rgb(154, 126, 127) — 중간
console.log(colorInterp(1));   // rgb(231, 76, 60) — 끝
💡

d3.interpolate()는 두 값 사이의 보간 함수를 자동으로 선택합니다. 숫자, 색상, 문자열, 배열, 객체 등 다양한 타입을 자동 감지하여 적절한 보간을 수행합니다.