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

– Data Visualisation

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

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

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

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

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

응. 안되더라.

고생 끝에 얻은 깨달음 💡

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

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

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

// [각 이슈별 해결방안 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-pros-and-cons
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-converting-glsl-to-webgl
KR-thepain-of-animation-debugging