DERMA:CODE Chart Library
30개 차트 동시 렌더링에서 76fps를 유지하는 Canvas 기반 의료 특화 시각화 라이브러리
DERMA:CODE 플랫폼의 의료 데이터 시각화를 위해 Canvas API를 기반으로 설계한 전용 차트 라이브러리입니다. 플랫폼 디자인 시스템과 긴밀하게 통합되며, GitHub Packages로 배포되어 독립적인 확장성을 갖췄습니다.
The Challenge
DERMA:CODE 플랫폼은 6가지 지표로 피부 점수를 표현하는 육각형 방사형(Hexagon) 차트 등, 기존 범용 라이브러리로는 구현하기 까다로운 의료 특화 시각화가 필요했습니다. 또한, 플랫폼 전반의 시각적 정체성을 유지하기 위해 일관된 디자인 시스템 위에서 정교하게 동작해야 했습니다.
- 도메인 특화 시각화의 한계: D3, Chart.js 등은 육각형 차트나 레벨(Level) 차트 같은 커스텀 레이아웃 구현에 제약이 많음.
- 디자인 일관성 부족: 차트별로 개별 스타일링을 적용하다 보니 플랫폼 전체의 시각적 통일성이 저하됨.
- 렌더링 성능 이슈: 상담 화면에서 다수의 차트가 동시 렌더링될 때, SVG 기반 방식은 DOM 부하로 인한 프레임 드랍 발생.
- 재사용성 요구: 타 프로젝트에서도 즉시 도입할 수 있도록 플랫폼 로직과 분리된 독립 패키지 구조가 필요함.
Technical Decisions & Approach
플랫폼 종속성을 제거하면서도 디자인 시스템과 유연하게 연동되는 구조를 목표로 삼았습니다. Canvas API 기반 렌더링 엔진 위에 테마 시스템과 7종의 도메인 특화 차트를 설계했습니다. 모노레포 구조로 라이브러리와 데모 앱을 통합 관리하고, Storybook을 통해 인터랙션과 테마 변형을 문서화했습니다.
SVG 대신 Canvas API를 선택한 이유
상담 화면에서 Hexa, 바, 라인 등 5개의 차트가 동시에 노출되며, 모두 애니메이션이 포함됩니다.
SVG는 각 차트 요소가 DOM 노드로 존재하여 이벤트 핸들링과 접근성 면에서 유리하지만, 차트 수가 늘어날수록 DOM 객체 증가로 리플로우 비용이 커졌습니다. 실제 상담 화면에서 SVG 기반 프로토타입을 테스트했을 때 차트 10개 이상에서 프레임 드랍이 관찰되었습니다. Canvas는 DOM에 영향을 주지 않아 성능은 우수하지만, 접근성(a11y)과 개별 요소 이벤트 처리를 직접 구현해야 하는 트레이드오프가 있었습니다. 상담 현장에서 차트를 직접 클릭하는 인터랙션은 제한적이고, 성능 안정성이 더 중요하다고 판단하여 Canvas를 선택했습니다.
직접 구축한 벤치마크 앱에서 Canvas/SVG/Recharts 세 렌더러를 동일 조건으로 비교했습니다. SVG 대비 프레임 유지율 약 7배(11fps → 76fps) 향상, 인터랙션 레이턴시 68% 단축(27.5ms → 8.8ms)을 확인했습니다. (CPU 4x Throttling 기준, No Throttling에서는 Canvas와 SVG 모두 76fps로 동일)
D3, Chart.js 대신 커스텀 라이브러리를 구축한 배경
세 가지 선택지를 검토했습니다. D3는 자유도는 높지만 추상화 수준이 낮아 차트 하나를 그리는 데도 수십 줄의 로우레벨 코드가 필요합니다. 7종의 커스텀 차트를 모두 D3로 구현하면 코드량이 방대해지고, 유지보수 시 매번 D3 API를 깊이 이해해야 합니다. Chart.js는 사용은 간편하지만 기본 제공 차트 타입에서 벗어난 확장(특히 Hexa 차트)이 어렵고, 플러그인으로 해결하려 해도 내부 렌더링 파이프라인에 종속됩니다. 커스텀 구축은 초기 개발 비용은 크지만, 의료 도메인에 맞는 차트 타입을 자유롭게 설계할 수 있고, 디자인 시스템과의 통합도 완전히 제어 가능합니다.
7종의 도메인 특화 차트를 장기적으로 유지보수해야 하는 상황에서, 기존 라이브러리를 커스터마이징하는 비용이 직접 구축하는 비용을 넘을 것으로 판단하여 커스텀 라이브러리를 선택했습니다.
디자인 토큰 기반 테마 시스템 설계
색상, 타이포그래피, 간격, 투명도 등을 디자인 토큰(Design Tokens)으로 정의하고 Light, Dark, Minimal, Colorful 등 4가지 프리셋을 제공했습니다. 토큰 단위의 오버라이드를 지원하여 개별 차트의 커스터마이징 허용과 전체 UI 일관성이라는 두 마리 토끼를 잡았습니다.
tsup과 pnpm 모노레포 구조 선택
라이브러리와 이를 사용하는 Storybook/데모 앱을 별도 저장소로 관리하면, 수정할 때마다 '빌드→npm 배포→설치→확인'의 4단계를 거쳐야 합니다. 개발 속도가 핵심이었기 때문에 모노레포로 '수정→즉시 확인'의 2단계로 줄이는 구조를 선택했습니다.
pnpm workspace 기반 모노레포를 구축했습니다. pnpm은 심볼릭 링크 기반 workspace 프로토콜로 로컬 패키지 간 의존성을 자연스럽게 해결하며, npm/yarn 대비 디스크 사용량과 설치 속도에서 이점이 있어 선택했습니다. tsup으로 CJS/ESM 듀얼 번들을 지원하여 소비하는 프로젝트의 모듈 환경에 관계없이 사용 가능하게 했고, TypeScript 선언 파일(.d.ts)을 자동 생성하여 라이브러리 사용자가 IDE에서 차트 Props, 테마 설정, 데이터 타입에 대한 자동완성과 타입 체크를 받을 수 있도록 했습니다.
GitHub Actions 기반 CI/CD 파이프라인 구축
단독 개발 환경에서도 코드 품질을 일관되게 유지하고, 배포 과정의 휴먼 에러를 제거해야 했습니다. 수동 배포는 버전 태깅 누락, 빌드 누락 등의 실수로 이어질 수 있었습니다.
GitHub Actions로 3단계 파이프라인을 구축했습니다. (1) PR/push 시 타입 체크 → 테스트 → 빌드를 순차 실행하는 CI, (2) Git 태그(v*) 푸시 시 GitHub Packages로 자동 배포하는 Publish, (3) src/ 또는 .storybook/ 변경 시에만 트리거되는 Storybook GitHub Pages 배포.
코드 변경부터 GitHub Packages 배포, 문서 배포까지 전 과정이 자동화되어, 단독 개발자로서도 안정적인 릴리스 사이클을 유지할 수 있었습니다.
Canvas 컴포넌트 테스트 전략
Canvas는 DOM이 없어서 querySelector 기반 검증이 불가능합니다. jsdom에는 Canvas 구현 자체가 없어 getContext('2d')가 null을 반환하므로, 일반적인 React 테스트 방식이 통하지 않습니다.
3-layer 테스트 전략을 설계했습니다.
Layer 1 — 행위 기반 검증: Canvas 2D Context를 vi.fn()으로 mock하여 '올바른 API가 올바른 인자로 호출되는지' 검증합니다. 렌더링, 테마 적용, 빈 데이터 처리 등 컴포넌트 수준의 행위를 커버합니다.
Layer 2 — 순수 함수 단위 테스트: Canvas 렌더링 자체보다 수학적 계산(히트 판정 로직)의 정확성에 집중하여 테스트 신뢰도를 높였습니다. 히트 판정 로직(detectVertexHit, detectLabelHit)을 순수 함수로 추출하여, mock 없이 좌표 입력 → 인덱스 출력을 검증합니다. 겹치는 영역(vertex + label tolerance 중복)과 경계값을 정밀하게 커버합니다.
Layer 3 — 통합 테스트: fireEvent + 콜백으로 이벤트 → 좌표 변환 → 히트 판정 → 콜백의 end-to-end 흐름을 검증합니다.
122개 테스트, 6개 파일. CI에서 PR마다 자동 실행됩니다.
Results & Impact
- 시각화 레이어 통합: 7종의 도메인 특화 차트를 단일 라이브러리로 관리.
- 개발 생산성 향상: 테마 시스템 도입으로 신규 화면 구축 시 별도의 스타일링 작업 없이 디자인 토큰만으로 UI 완성.
- 커뮤니케이션 비용 절감: 7종 차트 × 4 테마 × 다양한 Props 조합의 Storybook 스토리를 통해 기획/디자인 팀과 실제 구현물(Props, 인터랙션)에 대한 싱크를 맞춤.
- 높은 확장성: GitHub Packages 배포를 통해 타 프로젝트에서도 명령 한 줄로 즉시 도입 가능.
- 디자인 시스템 업데이트와 개발 구현체 간의 Single Source of Truth 확보.
라이브러리 도입 후 신규 차트 구현 시간이 획기적으로 단축되었으며, 50개 이상의 Storybook 스토리를 통해 디자이너가 직접 검수할 수 있는 환경을 제공하여 수동 QA 프로세스를 도메인당 3시간에서 1시간으로 단축(67%)했습니다.
Limitations & Future Improvements
현재 라이브러리의 기술적 한계를 인지하고 있으며, 다음 단계의 개선 방향을 설계 중입니다.
devicePixelRatio 미대응
고해상도 출력(PDF export) 대응을 위한 HiDPI 최적화가 백로그에 올라와 있습니다. devicePixelRatio 기반 스케일링을 적용하여 레티나 및 고DPI 환경에서도 선명한 출력을 보장할 계획입니다.
HexaChart 렌더링 로직 단일 파일 집중
히트 판정 로직(detectVertexHit, detectLabelHit)을 순수 함수로 분리 완료하여 테스트 가능성과 인지 부하를 개선했습니다. 렌더링, 좌표 계산, 애니메이션 분리는 오프스크린 Canvas 레이어 구조로 리팩토링을 진행 중입니다.
Canvas 접근성(a11y) 미처리
현재는 내부 전문가용 도구라 우선순위가 낮았으나, 향후 환자용 리포트 확장 시 ARIA 스크린 리더 지원을 최우선 과제로 검토 중입니다.