01
문제 정의
캔들·거래량·축·분석선은 데이터나 화면 범위가 바뀔 때 갱신되지만, 크로스헤어와 툴팁은 포인터가 움직일 때마다 바뀝니다. 두 종류의 요소를 하나의 Canvas에 그리면 포인터 이동만으로도 전체 차트를 반복해서 다시 그리게 됩니다.
- 변경 빈도가 다른 요소가 같은 렌더링 루프에 묶여 있었습니다.
- 포인터 이동은 초당 많은 이벤트를 발생시킵니다.
- 정적 차트까지 다시 그리면 불필요한 연산과 페인팅이 증가합니다.
02
Layer Separation
| 레이어 | 표현 요소 | 갱신 시점 |
|---|---|---|
| Base Canvas | 캔들, 거래량, 가격·시간 축, 지지·저항선 | 데이터·범위·크기 변경 |
| Overlay Canvas | 크로스헤어, 포인터, 툴팁, 선택 상태 | 포인터 이동·사용자 인터랙션 |
┌────────────────────────────┐
│ Overlay Canvas │ ← pointer / tooltip / hover
├────────────────────────────┤
│ Base Canvas │ ← candle / volume / axis / line
└────────────────────────────┘03
Rendering Flow
Data / Viewport Change ──→ drawBase()
Pointer Move ─────────────→ drawOverlay()
↓
requestAnimationFrame- 데이터 또는 화면 범위가 바뀌면 Base Canvas를 갱신합니다.
- 포인터 좌표는 차트 좌표계로 변환합니다.
- 크로스헤어·툴팁 상태를 Overlay Canvas에만 그립니다.
- requestAnimationFrame 기준으로 여러 입력을 한 번의 화면 갱신에 묶습니다.
04
좌표와 인터랙션
| 변환 | 역할 |
|---|---|
| Time → X | 시계열 인덱스 또는 시간을 화면 가로 좌표로 변환 |
| Price → Y | 가격 범위를 화면 세로 좌표로 변환 |
| Pointer → Data | 마우스 위치에서 가장 가까운 시점과 가격을 계산 |
렌더링 레이어는 분리했지만 같은 좌표 변환 규칙을 사용해 Base Canvas의 데이터와 Overlay Canvas의 포인터 UI가 정확히 일치하도록 구성했습니다.
05
Before / After
| 구분 | 단일 Canvas | 2-Layer Canvas |
|---|---|---|
| 포인터 이동 | 전체 차트 다시 그리기 | Overlay만 갱신 |
| 렌더링 책임 | 정적·동적 요소 혼합 | 변경 빈도에 따라 분리 |
| 성능 분석 | 병목 범위가 넓음 | 레이어별 갱신 조건 확인 가능 |
무조건 덜 그리는 것보다, “무엇이 언제 변하는가”를 기준으로 렌더링 책임을 나누는 것이 핵심이었습니다.
06
결과
- 포인터 이동 시 Overlay Canvas만 갱신합니다.
- 정적 차트와 고빈도 UI의 갱신 조건이 분리되었습니다.
- 차트 엔진의 렌더링 흐름과 디버깅 범위가 명확해졌습니다.