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
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 ]]