logo
복합 차트의 자유도를 찾아서

복합 차트의 자유도를 찾아서

최근 거래 데이터를 시각화하는 과정에서, 일반적인 차트 라이브러리로는 해결하기 힘든 복합 차트(Composite Chart) 요구사항을 마주했다. 단일 차트가 아닌, 서로 다른 성격의 데이터를 하나의 좌표계 위에 중첩하여 표현하는 것이 핵심이었다.

구체적으로 구현해야 했던 차트의 스펙은 다음과 같았다.

  • 차트 1: Depth Chart (매수/매도 잔량)
  • 차트 2: 거래량(Bar) + 이동평균선(Line) + 체결 내역(Scatter) 혼합
  • 차트 3: n개의 가격 추이 비교

차트1: Depth Chart, 매수/매도

차트2: 거래량(Bar) + 이동평균선(Line) + 체결 내역(Scatter)

차트3: n개 가격추이

기술선정: 왜 Reaviz인가?

초기에는 react생태계에서 점유율이 높은 라이브러리들을 우선 검토했다. (rechartsApexChartsvictory 등등...)

이 라이브러리들은 공통적으로 “시작하기 쉬운 조건”을 갖고 있다.

그 이유는

  • 차트를 곧바로 그릴 수 있도록 API가 미리 세팅되어 있음
  • 구현 난이도가 비교적 낮음.
  • 사용자가 많고, 관련 문서/자료가 많음

그런데 이러한 라이브러리로 복합 차트를 만들기 시작하면 금방 한계점에 부딪힌다.

왜냐하면 차트별 정교한 위치 조정이 어렵고, 서로 다른 도메인(Y축 스케일 등) 데이터를 한 차트에 동시에 표현하는 데 제약이 있기 때문이다. 툴팁을 부분별로 다르게 표현하는 것도 불가능하거나 매우 복잡했다.

우리 팀에 필요했던 건 위와같은 "시작하기 쉬운 조건"이 아니라,

  • d3.js 수준의 자유도를 가지되, 생산성은 더 높을 것
  • 그럼에도 과하게 어렵지 않은 API 사용성
  • 복합 차트 구현에 유연해야함

…같은, 현실적으로 꽤 욕심 많은 조건이었다. (현실은 항상 빡셈)

그래서 “커스터마이징”을 최우선 기준으로 다시 리서치했고, 최종적으로 reaviz 라는 차팅 라이브러리를 선택하게 됐다.

참고로 이 과정에서 Visx도 진지하게 고민했는데, d3를 얇게 감싼 구조라 자유도는 최상급이었다. 대신 너무 로우 레벨이라 팀 차원에서 “굳이 여기까지 내려가진 말자”는 결론이 났고, reaviz로 결정했다.

Reaviz: “조립”할 수 있는 구조

Reaviz는 차트의 뼈대가 되는 ChartContainer 안에 BarSeriesLineSeries 같은 다양한 Series 컴포넌트들을 자식(children)으로 넣어 조립하는 방식을 취한다.

거래량 막대그래프가 필요하면 BarSeries 컴포넌트를 끼워 넣고, 그 위에 라인그래프가 필요하면 LineSeries 컴포넌트를 추가하면 되는 식이다. 

reaviz에서 대부분의 차트 컴포넌트가 이렇게 독립적으로 분리되어 있다 보니, 언뜻 보기에는 내가 원하는 대로 자유롭게 레이어를 쌓아 올리기만 하면 복합 차트가 뚝딱 완성될 것처럼 직관적이어 보였다.

하지만 막상 개발 깊숙이 들어가자, 이 유연함 뒤에 숨겨진 명확한 Trade-off를 마주하게 되었다.

단점 1: 예측하기 힘든 내부구현, cloneElement 남용

가장 당황스러웠던 점은 Reaviz 측의 내부 소스코드에서 cloneElement를 통해 부모 컴포넌트가 자식의 Props를 강제로 덮어씌우는 패턴이 빈번하다는 것이다. 이는 Reaviz가 모든 요소를 독립적인 컴포넌트로 잘게 쪼개놓은 구조였기 때문에 발생하는 부작용이었다.

각 컴포넌트가 독립적으로 존재하다 보니, 부모 컴포넌트가 자식에게 필요한 상태나 동작을 전달하기 위해 cloneElement로 Props를 주입하는 방식을 택한 것이다.

문제는 이 과정에서 가끔 "내가 전달한 코드"는 무시된채 내부 로직이 무작정 내 코드를 덮어씌워버리는 상황이 발생한다는 점이다.

아래 예시코드에서 disabled 를 다음과 같이 전달하면, 보통은 툴팁이 비활성화 상태가 될 것을 예상한다. (아마도...)

<AreaSeries
  // ... 생략
  tooltip={
    <TooltipArea
      tooltip={
        <ChartTooltip
          disabled={true} 👈👈
          content={() => <div>Tooltip ontent</div>}
        />
      }
    />
  }
/>

하지만 실제로 disabled가 내부코드에서 무시된 채 툴팁이 버젓이 렌더링 된다....?! 

이유는 부모인 TooltipArea가 내부에서 cloneElement를 사용해 자식을 복제할 때, 자신의 로직에 따라 disabled 속성을 강제로 덮어씌워 버리기 때문이다.

적어도 Props를 받을 거면 동작해야 하고, 동작을 안 시킬 거면 차라리 받지 않는 게 API 신뢰도 측면에서 맞지 않을까 싶었다...

(당연한 건가..?)

단점 2: 생각보다 빈약한 문서

동작이 예상과 다르니 자연스럽게 문서를 찾게 되는데, 여기서 두 번째 문제가 터진다.

공식 문서가 대부분 단일 차트(Single Chart) 기준으로만 정리되어 있었다.

즉, 우리가 구현하려던 복합 차트에 필수적인 내용들은 정작 문서에서 빠져있는 부분이 많았다.

cloneElement로 인해 Props가 무시되는 상황에서 문서조차 얇으니, 결국 해결책은 하나뿐이었다.

코드를 로컬로 내려받고 내부 소스 코드를 직접 까보며 개발하는 것....

또는, 오픈소스를 Claude Code에 학습시키고 질문하는 것....

결국 구현 과정은 아래와 같은 삽질의 반복이었다.

  • 공식 문서 확인 → 내용 없음 (1차 좌절)
  • node_modules 내부 소스 코드 열기 → 실제 구현 파악
  • 적용 시도 → 예상과 다르게 동작하면 또다시 코드 확인

d3.js로 바닥부터 만드는 것보다는 낫겠지만, 이런 상황이 가끔 반복될 때마다 라이브러리를 사용하는 이점이 희석되는 느낌이었다.

단점 3: DX를 잃은 내부 구현 (Recharts와의 차이)

그렇게 내부 코드를 뜯어보며, 왜 이런 불편함이 생겼는지 설계적인 이유를 알게 되었다.

내가 겪은 불편함은 단순한 버그가 아니라, "자유도"를 얻기 위해 포기한 "구조적 안정성"에서 오는 것이었다.

Reaviz: 거의 모든 기능이 독립적인 컴포넌트로 분리되어 있고 Provider 의존성이 없음.

→ 장점: "블록 조립"하듯 자유로운 배치가 가능함.

→ 단점: 부모-자식 간 데이터 전달을 위해 cloneElement 같은 무리한 패턴이 등장함.

Recharts / Victory: Provider 의존성이 강함.

→ 단점: 컴포넌트를 개별적으로 떼어내어 사용하기 어려움.

→ 장점: Context를 통해 상태를 공유하므로 동작이 예측 가능하고 구조가 명확함.

결국 Reaviz는 "높은 커스터마이징 자유도"를 선택하는 대신 안정적인 "DX(개발자 경험)"를 희생한 셈이다.

TooltipArea 버그와 기여

Reaviz를 서비스에 붙이고 나니 예상치 못한 버그도 몇 번 나왔다. 그중 가장 인상적이었던 건 툴팁 위치 증발 이슈였다.

재현 조건

차트 위에 툴팁이 떠 있는 상태에서 Cmd+Tab으로 다른 앱에 다녀오면, 툴팁이 갑자기 화면 좌상단(0,0) 좌표로 튀어버린다.

원인 분석

근본적인 원인은 툴팁의 visible 상태가 바뀔 때 컴포넌트의 Mount/Unmount가 반복되는 데 있었다.

툴팁 컴포넌트가 다시 마운트될 때, 이전의 내부 상태(특히 마우스 위치 컨텍스트)를 잃어버리면서 기본 좌표값인 (0,0)으로 초기화되는 것이었다. (물론 이후 마우스를 살짝이라도 움직이면 다시 정상 위치로 돌아온다.)

대응

사용자 경험(UX) 관점에서 그냥 넘기기엔 너무 어색한 버그였다. 결국 코드를 파고들어 해당 로직을 수정했고, PR을 올려둔 상태다.

(PR: https://github.com/reaviz/reaviz/pull/312)

정리.. 자유도와 DX는 같은 축에 있지 않다

Reaviz는 “자유로운 커스터마이징”이라는 목표에는 확실히 성공했다고 본다.

하지만 그만큼의 대가로 개발자가 치러야 할 Trade-off가 컸고, DX 관점에서는 아쉬움이 남았다.

"이번 경험을 통해 자유도와 DX는 반비례 관계에 가깝다는 것을 뼈저리게 배웠다."

만약 다시 선택의 순간이 온다면, 나는 이런 기준으로 결정할 것이다.

  • 복합 차트를 아주 강하게 커스텀해야 한다? → Reaviz가 훌륭한 후보가 될 수 있다.
  • 예측 가능성과 안정적인 DX가 최우선이다? → Recharts 같은 전통적인 선택지가 훨씬 낫다.

Written on

2025-11-09 09:25

Comments