Chart.js -> Remotion 비디오에 강제 이식 성공기

– Data Visualisation

데이터 저널리즘, 데이터를 보기 좋게 시각화해서 영상으로 만들기. 이왕 비상계엄 경제지표 타임라인 페이지를 통해 Chart.js로 데이터 시각화를 한 컴포넌트들이 있으니 “아, 저거 그대로 영상에 쓸 수 있으면 개꿀인데?” 싶었다.

그래서 이번 목표는 “기존 Chart.js 데이터 시각화 자산을 활용해서 고품질 데이터 저널리즘 비디오를 만들자! “ 였다.

일단 있는 거부터 어떻게든 엮어! 🔗

가장 먼저 한 건 역시 Remotion 판을 짜는 거.

Chart.js 차트, Remotion으로 이사시키기: 이게 첫 번째 관문. 이미 잘 돌아가는 Chart.js 컴포넌트들을 Remotion 비디오의 한 장면, 한 장면으로 어떻게 옮길 수 있을까? 그냥 복붙하면 될 줄 알았는데…

응. 안되더라.

고생 끝에 얻은 깨달음 💡

Chart.js를 Remotion에 욱여넣으면서 제일 크게 부딪혔던 문제는 바로 렌더링 동기화.

Chart.js는 캔버스에 그림을 그리는데, 이게 Remotion이 프레임을 캡처하는 타이밍이랑 딱 맞아야 하…지만 캔버스가 렌더링을 밍기적거리는 사이에 Remotion은 텅 빈 화면이나 반쯤 그려진 차트를 캡처해버리는 것이다…이런 ㅅㅂㄹㅁ 🤬

이거 해결하려고 진짜 별짓 다 해봤다. 그러다 구세주처럼 등장한 게, Remotion의 delayRendercontinueRender 함수.

// [각 이슈별 해결방안 A: 렌더링 동기화 해결]
// 대충 ChartWrapper.tsx 안에서 이렇게 썼다는 이야기
import { useState, useEffect } from 'react';
import { delayRender, continueRender, useCurrentFrame } from 'remotion';

const ChartWrapper = ({ chartData, chartOptions, chartType }) =%3E {
  const frame = useCurrentFrame();
  // 일단 Remotion한테 "잠깐만! 렌더링 좀 멈춰봐!" 신호를 보낸다
  const [handle] = useState(() => delayRender());

  useEffect(() => {
    // 차트 라이브러리가 캔버스에 그림 그릴 시간 좀 주고...
    // 여기서는 그냥 setTimeout으로 퉁쳤지만, 실제로는 차트 렌더링 완료 콜백 같은 걸 쓰면 더 좋겠지?
    const timeout = setTimeout(() => {
      // "자, 이제 그려도 돼!" 하고 신호를 다시 보내는 거지
      continueRender(handle);
    }, 100); // 100ms 정도면 넉넉하려나? (사실 이건 상황 따라 다름)

    return () => clearTimeout(timeout);
  }, [handle, chartData, chartOptions, chartType, frame]); // 의존성 배열 꼼꼼히!

  // (이하 Chart.js 렌더링 로직 생략)
  return %3Ccanvas id={`chart-${frame}`} />; // 프레임마다 다른 ID를 주는 센스!
};

delayRender로 일단 Remotion의 렌더링을 홀드시키고, Chart.js가 충분히 렌더링할 시간을 준 다음에 continueRender로 “이제 찍어도 돼!” 하고 알려주는 거지. 이거 발견했을 때 진짜 “유레카!” 외쳤다니까. 아, 물론 저 100ms는 임시방편이다. 실제로는 Chart.js가 “나 다 그렸어요!” 하는 콜백을 받아서 처리하는 게 안정적일 것이다. (나중에 리팩토링 할 거야, 진짜로… 아마도…)

[도출한 결론들 B: 디자이너 친화적 워크플로우 - 현재 위치]

자, 기술적인 문제들은 어찌어찌 해결했는데, 진짜 중요한 건 따로 있었다.

[발견한 이슈들 C: 코드 복잡성으로 인한 디자이너 접근성 문제]

데이터 저널리즘 비디오는 보통 슬라이드 여러 개가 쭉 이어지는 형식이잖아? 근데 슬라이드 내용 하나 바꾸자고 매번 React 컴포넌트 코드를 직접 건드려야 한다? 이건 좀 아니지. 귀찮아!!!

그래서 머리를 쥐어짜낸 결과, 슬라이드 구성을 위한 설정 파일을 따로 빼기로 했다. 이름하여 chartSequence.ts!

// [각 이슈별 해결방안 C: 선언적 슬라이드 구성]
// VideoEditPlayground/src/remotion/_shorts/DataJournalism/config/chartSequence.ts
// 디자이너님은 이제 이 파일만 건드리면 됩니다! YES!
export interface ChartSequenceItem {
  id: string; // 각 슬라이드 고유 ID
  indicatorId: string; // 어떤 데이터를 보여줄 건지 (data/index.ts 파일에 정의된 ID)
  title?: string; // 슬라이드 제목 (옵션)
  durationInSeconds: number; // 이 슬라이드 몇 초 동안 보여줄겨?
  chartType?: 'bar' | 'line' | 'doughnut' | 'table'; // 어떤 차트 모양으로? (옵션, 기본값 지정 가능)
  // 기타 필요한 설정들 쭉쭉 추가 가능!
}

export const chartSequence: ChartSequenceItem[] = [
  {
    id: "slide1_exchange_rate",
    indicatorId: "exchangeRateUS_1203", // 미국 환율 데이터!
    title: "최근 미국 환율 추이",
    durationInSeconds: 7,
    chartType: "line",
  },
  {
    id: "slide2_stock_price",
    indicatorId: "samsungElectronicsStock_005930", // 삼성전자 주가 데이터!
    title: "삼성전자 주가 현황",
    durationInSeconds: 5,
    chartType: "bar",
  },
  // ... 이런 식으로 슬라이드 쭉쭉 추가하면 됨!
];

이렇게 하니까 복잡한 React 코드 들여다볼 필요 없이(내가 작성했어도 영상 작업마다 컴포넌트 코드들이 기본 10개가 넘고 10개 이상 컴포지션이 넘어가는 순간 햇갈리게 되더라…ㅋㅋ…ㅋㅋ),

chartSequence.ts 파일에서 필요한 데이터 ID랑 제목, 보여줄 시간 같은 것만 딱딱 적어주면 되는 거야. 선언적으로! 그럼 DataJournalismSequence.tsx 같은 메인 컴포넌트가 저 설정 파일을 읽어서 알아서 슬라이드들을 뿅뿅 만들어주는 거지 🤣 뿅뿅뾰로로로로 뿅뿅!

이런 우여곡절 끝에, 드디어 “쓸 만한 거 하나 나왔나?” 싶은 순간이 오더라. 물론 아직 갈 길이 멀지만, 그래도 중간 점검 한번 해봐야지 않겠어?

그래서 지금 내 손에 뭐가 들려있냐면…

[최종 목표 - 현재 위치]

내가 최종적으로 만들고 싶었던 건 “기존 Chart.js 데이터 시각화 자산을 활용한 고품질 데이터 저널리즘 비디오 제작 시스템”

  • 데이터 시각화: Chart.js 차트들, 비디오에서 아주 그냥 쌩쌩 돌아가게!
  • 자동화: 슬라이드 정보만 쓱 넣어주면 비디오가 자동으로 뚝딱!
  • 디자이너 친화적: 디자이너님, 이제 코드 그만 보시고 이 설정 파일만 고쳐주세요!
  • 재사용성: 여기서 만든 컴포넌트들, 다른 프로젝트에서도 또 써먹을 수 있게!

[프로젝트 구조 및 데이터 흐름 > 파일 구조 (ASCII) - 현재 위치]

대충 지금 내 프로젝트 폴더는 이렇게 생겨먹었다.

VideoEditPlayground/src/remotion/

├── _shorts/ # 짧은 영상들 모아두는 곳 (데이터 저널리즘 비디오 같은 거)
│   └── DataJournalism/
│       ├── Index.tsx                # Remotion 컴포지션 정의하는 파일
│       ├── DataJournalismSequence.tsx # 메인 시퀀스 로직! 얘가 다 함
│       ├── ChartWrapper.tsx         # Chart.js를 감싸주는 고마운 래퍼
│       ├── chartLayouts/            # 실제 Chart.js 컴포넌트들 (종류별로)
│       ├── data/index.ts            # 차트에 쓸 데이터들 모아놓는 곳
│       └── config/                  # 설정 파일들! 여기가 핵심!
│           ├── chartSequence.ts     # ✨디자이너님이 편집하는 바로 그 파일✨
│           └── types.ts             # 타입 정의 파일 (TypeScript는 소중하니까)

핵심은 _shorts/DataJournalism/config/chartSequence.ts 파일이랑 이걸 읽어서 처리하는 DataJournalismSequence.tsx 그리고 ChartWrapper.tsx 겠지. _Shared 폴더에는 재사용 가능한 셰이더 관련 코드들이 들어있고.

[프로젝트 구조 및 데이터 흐름 > 데이터 흐름 (Mermaid) - 현재 위치]

데이터가 대충 어떻게 흘러가는지는 역시 그림으로 보는 게 이해가 빠르지.

데이터 흐름 (Mermaid)

graph TD
    A[chartSequence.ts] --> B[DataJournalismSequence.tsx]
    C[data/index.ts] --> B
    B --> D[ChartWrapper.tsx]
    D --> E[Chart Components]
    E --> F[Rendered Charts]
    
    G[VideoShader.tsx] --> H[TriChrome2.tsx]
    I[Shadertoy Code] --> H
    H --> J[MoShaderShow.tsx]
    
    B --> K[Series Component]
    K --> L[Individual Slides]
    L --> M[Final Video]
    
    style A fill:#e1f5fe
    style C fill:#e1f5fe
    style I fill:#fff3e0
    style M fill:#e8f5e8

컴포넌트 추상화 및 프로퍼티 흐름

graph LR
    subgraph "Data Layer"
        A1[indicators data]
        A2[chartSequence config]
    end
    
    subgraph "Presentation Layer"
        B1[DataJournalismSequence]
        B2[ChartWrapper]
        B3[Chart Components]
    end
    
    subgraph "Effect Layer"
        C1[VideoShader]
        C2[TriChrome2]
    end
    
    A1 --> B1
    A2 --> B1
    B1 --> B2
    B2 --> B3
    C1 --> C2
    
    B1 -.-> C2
graph TD
    subgraph "✨ 디자이너 영역 ✨"
        A["chartSequence.ts<br>(슬라이드 순서/내용 설정)"] -- 설정 정보 --> B
    end

    subgraph "📊 데이터 영역"
        C["data/index.ts<br>(차트 원본 데이터)"] -- 실제 데이터 --> B
    end

    subgraph "⚙️ Remotion 처리 영역 ⚙️"
        B["DataJournalismSequence.tsx<br>(설정+데이터 기반 슬라이드 생성)"] -- 차트 렌더링 요청 --> D["ChartWrapper.tsx<br>(Chart.js 래핑 및 동기화)"]
        D -- 렌더링 지시 --> E["Chart Components<br>(개별 차트 로직)"]
        E -- 그려진 차트 --> F[Rendered Charts on Canvas]

        G["VideoShader.tsx<br>(공통 셰이더 로직)"] -- 셰이더 기능 제공 --> H["TriChrome2.tsx<br>(구체적인 셰이더 효과)"]
        I[Shadertoy 원본 코드 변환] -- GLSL 코드로 --> H
        H -- 비디오 프레임에 효과 적용 --> J["MoShaderShow.tsx<br>(셰이더 적용된 비디오 부분)"]
        
        B -- 개별 슬라이드 조합 --> K[Remotion Series Component]
        K -- 시퀀스에 따라 --> L[개별 슬라이드 비디오 프레임]
        F -- 각 프레임에 차트 삽입 --> L
        J -- (필요시) 특정 프레임에 셰이더 효과 --> L
        L -- 최종 조합 --> M["🎬 최종 비디오 출력"]
    end
    
    style A fill:#e1f5fe,stroke:#333,stroke-width:2px
    style C fill:#e1f5fe,stroke:#333,stroke-width:2px
    style M fill:#e8f5e9,stroke:#333,stroke-width:4px

이로서 chartSequence.ts 파일만 건드리면, DataJournalismSequence가 데이터랑 합쳐서 ChartWrapper를 통해 차트를 그리고 최종 비디오를 만들어내는…흐아앙 이거지예ㅠㅠㅠㅠ


아직 뭐 애니메이션 효과도 더 넣어야 하고, 나레이션 연동 같은 것도 해야 하고, 성능 최적화는 끝도 없겠지만… 그래도 이 정도면 “쓸 만한 거 하나 만들었다!“고 자부해도 되지 않을까? (아님 말고 ㅋㅋ)

여튼 작업하는 프로세스를 개선했다는 게 제일 뿌듯하다.

✅ 완료된 작업들

  • Remotion 프레임워크 이해 및 설정
  • Chart.js → Remotion 통합 솔루션 (ChartWrapper)
  • 유저 친화적 설정 시스템 (chartSequence.ts)
  • 재사용 가능한 쉐이더 인프라 (VideoShader.tsx)
  • 데이터 저널리즘 슬라이드 시스템 설계

다른 리모션 포스팅을 보고 싶다면

2025-01-16-implementing-canvas-animation-to-remotion-kr
2025-03-18-making-my-own-video-editor-in-progress
KR-no-more-adobe-starting-remotion-kr
KR-remotion-gsap
KR-figma-to-jitter-and-remotion
KR-converting-bodymovin-animation-to-remotion-template
KR-how-i-make-a-youtube-series-with-remotion-dynamic-srt-script-setup-exp-likejennie
KR-how-i-made-podcast-interview-with-ai-virtual-idol
KR-remotion-and-shader
[[ _notes/KR/WebDevelopment/KR-thepain-of-animation-debugging ]]