Plotly 차트가 작게 표시되는 문제, ResizeObserver로 안정적으로 해결
최근 Vue 프로젝트에서 Plotly.js를 활용해 데이터를 시각화하는 기능을 구현하면서, 조금 난감한 문제를 하나 마주했습니다. 바로 차트가 너무 작게 그려지는 현상 처음엔 단순 스타일 문제인가 싶었는데, 알아보니 생각보다 복잡한 렌더링 타이밍 이슈였습니다.
🔍 처음 마주한 문제: 작게 그려지는 Plotly 차트
처음엔 아래와 같은 상황이 반복됐습니다:
- 컨테이너(div)는 충분히 넓은데 차트는 구석에 아주 작게 렌더링됨
- 새로고침하거나 다른 메뉴 갔다가 돌아오면 정상 크기로 표시됨
setTimeout
,nextTick
등 다양한 방법을 써봐도 간헐적으로 다시 작게 나오는 현상 발생
즉, 초기 렌더링 시점에서 Plotly가 제대로 된 컨테이너 크기를 인식하지 못하는 것이 문제.
🧭 원인 분석: 렌더링 타이밍 & 경쟁 상태(Race Condition)
Vue에서는 DOM 요소가 렌더링된 직후 바로 Plotly 차트를 그리는 경우가 많습니다. 그런데 이 타이밍이 애매해서 브라우저가 아직 div의 실제 크기를 계산하기 전일 수 있습니다.
그 결과:
과정 | 설명 |
---|---|
1. Vue가 div 렌더링 | v-if , v-show 등에 의해 DOM 생성 |
2. 브라우저가 크기 계산 | 아직 완료되지 않았을 수 있음 |
3. Plotly가 차트 렌더링 | 이때 잘못된 크기를 기준으로 렌더링 |
4. 이후 컨테이너는 정상 크기로 확장됨 | 하지만 Plotly는 리렌더링 안 함 |
결국 이건 시점 문제였고, 이걸 안정적으로 감지해줄 방법이 필요
✨ 해결 방향: ResizeObserver로 리사이징 감지
Plotly에는 이미 Plotly.Plots.resize()
라는 리사이즈 전용 API가 있습니다.
여기서 중요한 건, 언제 이걸 호출해주느냐입니다.
그래서 선택한 방식이 바로 ResizeObserver였어요.
이 API를 사용하면 특정 요소의 크기 변화를 실시간으로 감지할 수 있어서, Plotly 차트도 그에 맞춰 리사이즈해줄 수 있죠.
⚙️ 작업 내용 정리
작업 항목 | 상세 내용 |
---|---|
✅ ResizeObserver 도입 | 차트 div 크기 변화 감지 |
✅ DOM 렌더 이후 연결 | mounted → $nextTick 으로 안정적 실행 보장 |
✅ Plotly 리사이즈 호출 | Plotly.Plots.resize() 사용 |
✅ 메모리 누수 방지 | beforeDestroy 에서 unobserve() 처리 |
코드 예시
import Plotly from 'plotly.js-dist'
export default {
data() {
return {
resizeObserver: null,
}
},
mounted() {
this.$nextTick(() => {
const chartDiv = this.$refs.chartDiv;
if (chartDiv) {
this.resizeObserver = new ResizeObserver(() => {
Plotly.Plots.resize(chartDiv);
});
this.resizeObserver.observe(chartDiv);
}
});
},
beforeDestroy() {
if (this.resizeObserver && this.$refs.chartDiv) {
this.resizeObserver.unobserve(this.$refs.chartDiv);
}
this.resizeObserver = null;
}
}
템플릿에서는 아래처럼 ref
를 꼭 달아줘야 합니다:
<div ref="chartDiv" style="width: 100%; height: 100%;">
<!-- Plotly chart will render here -->
</div>
💡 작업 중 느낀 점
이런 뷰 관련 타이밍 이슈는 언제나 애매하고 디버깅도 번거롭지만,언제 렌더링되는가와 언제 그리게 할 것인가를 분리하는 게 확실한 해결책이라는 걸 다시 느꼈습니다.
Vue의 mounted
, $nextTick
같은 훅만으로는 완전히 제어할 수 없던 시점 문제를
브라우저 레벨에서 감지할 수 있는 ResizeObserver
로 해결한 건 꽤 깔끔한 접근이었습니다.
✍️ 마무리하며
Plotly 같은 외부 라이브러리는 내부 렌더링 로직이 정해져 있어서, Vue의 반응형 렌더링 흐름과 충돌하는 경우가 종종 있습니다. 이번 문제도 그 중 하나였고, ResizeObserver 덕분에 보다 안정적이고 확장 가능한 방식으로 해결할 수 있습니다.
비슷한 문제 겪는 분들께 도움이 되길 바라며, 추후 Vue 3 + Composition API로도 적용해보겠습니다. 🙌