교체할 것인가 남길 것인가: DataLens가 Highcharts에서 마이그레이션한 방법

안녕하세요, 저는 Evgeny Alaev입니다. Yandex DataLens 팀의 인터페이스 개발자입니다. DataLens는 데이터 분석과 대시보드 구축을 위한 클라우드 BI 도구로, 차트는 단순한 '기능 중 하나'가 아니라 제품의 핵심입니다. 사용자가 대시보드를 열면 가장 먼저 보이는 것이 시각화입니다. 바로 시각화가 “내 데이터에서 무슨 일이 일어나고 있는가?"라는 질문에 답합니다.

DataLens는 두 가지 형태로 운영됩니다 — Yandex 내부용과 외부 사용자용. 현재까지 총 1,830만 개 이상의 차트가 생성되었습니다. 이 모든 차트는 우리가 이야기할 시각화 라이브러리의 결과물입니다.

오랫동안 DataLens의 차트는 Highcharts를 기반으로 구축되었습니다. 처음에는 합리적인 선택이었습니다. 빠른 시작, 풍부한 차트 유형, 큰 커뮤니티. 하지만 BI 도구는 시간이 지남에 따라 복잡해집니다 — 동작에 대한 비표준 요구사항이 생기고, 일관된 스타일을 유지해야 하는 디자인 시스템이 생깁니다. 어느 순간 Highcharts는 도움이 되기보다 방해가 되기 시작했습니다.

이 글에서는 자체 오픈소스 시각화 라이브러리 @gravity‑ui/charts를 개발하기로 결정한 방법과 이유를 이야기합니다. 저와 동료는 이 라이브러리의 핵심 기여자이므로, Highcharts에서 무엇이 불만족스러웠는지, 어떤 대안을 검토했는지, 아키텍처는 어떻게 구성되어 있는지, 그리고 개발 과정에서 어떤 구체적인 기술적 과제에 직면했는지 자세히 설명하겠습니다.


편의에서 제약으로

라이선스와 벤더 종속

Highcharts는 상용 라이브러리입니다. 비상업적 사용에는 무료이지만, 제품이 수익화되는 순간 라이선스가 필요합니다. 이것 자체가 문제는 아닙니다 — 많은 팀이 유료 도구로 작업합니다. 문제는 사용 모델이 복잡해질 때 시작됩니다.

DataLens는 세 가지 배포 형태를 가집니다: 클라우드, 오픈소스, 온프레미스. 비자유 라이선스가 의존성에 포함되면 — 선택적이더라도 — 각 형태의 제공이 복잡해집니다.

라이선스 외에도 외부 로드맵에 대한 강한 의존성이 있습니다. 새로운 차트 유형을 원한다면? 벤더가 만들어 줄 때까지 기다려야 합니다. 툴팁 동작에서 버그를 발견했다면? 티켓을 열고 우선순위에 포함되기를 기대해야 합니다. 범례 클릭 시 비표준 동작이 필요하다면? 문서나 Stack Overflow에서 해결책을 찾아야 합니다. 제품이 활발히 개발되고 시각화 요구사항이 비표준적일 때, 이런 의존성은 발목을 잡기 시작합니다.

별도의 문제는 메이저 업데이트입니다. 저희는 8번째 메이저 범위 내에서 최신 버전의 Highcharts를 사용했습니다. 다음 메이저로의 전환 자체도 비용이 컸지만, DataLens의 한 가지 특성이 특히 어렵게 만들었습니다: Editor 모드가 있어서 사용자들이 차트 설정을 위해 JavaScript 코드를 직접 작성합니다. 사실상 Highcharts를 직접 다루며 코드를 통해 설정을 구성합니다. 이런 사용자 코드는 자동으로 마이그레이션할 수 없습니다: 임의의 JS를 한 인터페이스에서 다른 인터페이스로 안전하게 변환하는 코드모드를 작성하는 것은 불가능합니다.

동작 제어

BI 도구에서 사용자는 차트와 활발하게 상호작용합니다: 범례를 클릭해 시리즈를 숨기고, 데이터 포인트 위에 마우스를 올리고, 툴팁을 고정합니다. 이런 각각의 시나리오는 Highcharts에서 콜백과 이벤트를 통한 세밀한 조정이 필요했습니다 — 항상 문서화된 것도 아니었고 버전 간에 항상 안정적이지도 않았습니다.

몇 가지 특징적인 예시입니다:

  • 툴팁 고정. Highcharts의 표준 툴팁은 마우스가 벗어나면 사라집니다. 8번째 메이저에는 “고정” 툴팁의 기본 지원이 없었습니다 — 이는 12번째 버전에서야 등장했습니다. 저희는 이것을 내부 메서드 오버라이드를 통해 구현했습니다: 마우스 이벤트를 가로채고 숨김 로직을 교체했습니다. 코드는 작동했지만 취약했습니다.

  • 커스텀 툴팁 렌더링. Formatter는 HTML 문자열을 반환하므로 연결을 통해 마크업을 수동으로 조립해야 했습니다. React도 없고, 컴포넌트도 없습니다. 결국 툴팁 렌더링은 자체 템플릿 로직을 가진 별도 레이어가 되었습니다.

  • 범례 클릭. 표준 동작은 시리즈 가시성을 토글합니다. 나머지를 망가뜨리지 않고 완전히 오버라이드하는 것은 비자명합니다: 핸들러를 내부 이벤트를 통해 연결하고 기본 동작을 주의깊게 취소해야 했습니다.

  • 테마. Highcharts의 색상, 폰트, 간격은 JavaScript 설정으로 정의되고 인라인 스타일로 적용됩니다. 그 위에 Gravity UI에서 팔레트가 업데이트될 때마다 수동으로 동기화해야 하는 기본 스타일 오버라이드용 별도 SCSS 파일을 유지했습니다.

이런 해결책들은 각각 고립된 문제가 아니라 전체 기술 부채에 기여합니다. 한 가지 해킹은 혼자 살아가지만, 두 가지는 머릿속에 담아둘 수 있어도, 열 번째에 이르면 서로 상호작용하기 시작합니다: 하나를 고치면 다른 것이 망가집니다. 유지보수 복잡도는 비선형적으로 증가하고, Highcharts의 각 업데이트는 감사로 변합니다: 이번에는 우리의 해결책 중 무엇이 조용히 망가졌을까.

대안 평가

직접 만들기 전에, 저희는 기존 솔루션을 솔직하게 검토했습니다. 주요 기준은:

  • 오픈 라이선스 — MIT 또는 호환 라이선스, 유료 티어 없음, OEM 계약 없음.

  • 렌더링 및 스타일의 완전한 커스터마이즈 — 해결책 없이 모든 요소의 외관을 제어할 수 있는 능력.

  • HTML 임베딩 — 툴팁, 차트 레이블, 축 레이블은 SVG 텍스트나 문자열 템플릿이 아닌 완전한 HTML 마크업으로 렌더링되어야 함.

ECharts, Recharts, Plotly, Chart.js, D3를 검토했습니다.

Chart.js와 Plotly는 빠르게 탈락했습니다: 기본 Canvas 렌더링은 CSS를 통한 스타일 커스터마이즈 가능성을 닫아버리고, 차트 요소에 임의의 HTML 임베딩도 지원하지 않습니다.

Recharts — React-first, SVG, MIT. 간단한 대시보드에 적합하지만, 깊은 형태 및 동작 커스터마이즈는 내부 API의 한계에 빠르게 부딪힙니다.

ECharts는 가장 가까운 옵션이었습니다: 풍부한 차트 유형, 유연한 설정, 커스텀 렌더러 지원. 하지만 자세히 보면 핵심 기준 중 하나를 충족하지 못했습니다: 차트의 임의적인 부분에 HTML을 완전히 임베딩하는 것. 저희 케이스에는 이것이 필수였습니다. 게다가 벤더 종속은 사라지지 않았습니다: MIT 라이선스가 법적 문제를 해결했지만, 외부 로드맵과 업데이트 사이클에 대한 의존성은 여전했습니다.

D3 — 차트 라이브러리가 아니라 기초 요소 모음입니다: 척도, 형태 생성기, 데이터 변환. 그 자체로는 문제를 해결하지 않지만, 그 위에 자체 솔루션을 구축하기 위한 완벽한 기초를 제공합니다.

결론은 논리적이었습니다: D3를 기반으로 삼고 그 위에 자체 라이브러리를 구축하는 것 — 이것이 자유와 제어를 모두 제공하고, 어떤 벤더에 대한 의존도 완전히 제거했습니다.

왜 D3인가, 다른 것은 안 되나

D3는 차트를 그리지 않습니다. 데이터와 DOM 작업을 위한 도구 모음입니다: 척도, 기하학적 형태 생성기, 변환. 바로 이것이 저희에게 필요한 것입니다 — 라이브러리 제작자가 제공하는 것에 제한받지 않고 어떤 시각화도 구성할 수 있는 기초 요소들.

D3는 축을 위해 scaleLinear, scaleBand, scaleUtc를, 형태를 위해 d3.line (), d3.arc (), d3.area ()를, 데이터 변환을 위해 d3.extent ()와 d3.group ()을 제공합니다. 나머지는 모두 저희 것입니다.

@gravity‑ui/charts 아키텍처

진입점 — 설정 객체

사용자는 데이터와 설정이 담긴 하나의 객체를 컴포넌트에 전달합니다: 시리즈, 축, 제목, 범례, 툴팁. 이것은 선언적 접근 방식을 선호하는 의식적인 선택입니다 — 설정의 대부분은 쉽게 직렬화되고, 로깅되고, 시스템 간에 전달될 수 있습니다.

선언적 설명이 충분하지 않은 곳에서 설정은 함수를 받습니다: 커스텀 툴팁 렌더러, 클릭 핸들러, 축 레이블 포매터. 이것은 모순이 아니라 의도적인 경계입니다 — 데이터 구조는 예측 가능하게 유지되고, 확장 지점은 명시적입니다.

데이터 흐름

라이브러리 내부에서 설정은 여러 처리 단계를 거칩니다:

  • 정규화. 입력 데이터가 통일된 내부 형식으로 변환됩니다. 이 단계에서 기본값이 설정되고, 모호함이 해소되며, 각 시리즈의 데이터가 타입화됩니다.

  • 시리즈 준비. 각 차트 유형(선, 막대, 파이 등)은 자체 모듈에 의해 처리됩니다. 색상이 할당되고, 범례를 위한 메타데이터가 형성됩니다.

  • 척도 및 축 구성. 데이터를 기반으로 D3는 척도를 구성합니다: 선형, 로그, 시간, 범주형. 척도는 데이터의 값을 픽셀 좌표로 변환하는 방법을 정의합니다.
  • 형태 렌더링. 각 시리즈 유형은 자체 SVG 요소를 그립니다: 선, 직사각형, 호, 점. D3는 형태 생성기를 제공하고, React는 요소의 라이프사이클을 관리합니다.

SVG + HTML: 하이브리드 렌더링

주요 렌더링은 SVG입니다. 이것은 선명한 경계, 품질 손실 없는 확장, 포지셔닝에 대한 완전한 제어를 제공합니다.

하지만 SVG는 텍스트를 줄바꿈할 수 없고, 요소 내에 풍부한 HTML 마크업을 지원하지 않습니다. 줄바꿈이 있는 데이터 레이블, 커스텀 툴팁, 차트 위의 대화형 요소에는 별도의 HTML 레이어가 사용됩니다 — SVG 위에 오버레이되고 좌표와 동기화되는 절대 위치의 div입니다.

이벤트 시스템

차트와의 사용자 상호작용 — 호버, 클릭, 마우스 이동 — 은 d3.dispatch 기반의 중앙화된 이벤트 버스를 통해 처리됩니다. 이를 통해 인터페이스의 다른 부분(툴팁, 크로스헤어, 범례)이 컴포넌트 간 직접 의존성 없이 하나의 이벤트에 독립적이고 일관되게 반응할 수 있습니다.

하지만 이 접근법에는 또 다른 중요한 결과가 있습니다 — 성능. 차트 위에서의 마우스 이동은 높은 빈도로 이벤트를 생성합니다. 각 이벤트마다 React 렌더를 시작하면 전체 컴포넌트 트리의 비용이 많이 드는 재계산으로 이어집니다. 대신 일부 업데이트 — 예를 들어 형태 호버, 포인터에 가장 가까운 점 찾기 — 는 React를 우회하고 D3를 통해 직접 적용됩니다. 컴포넌트는 다시 렌더링되지 않습니다: D3는 단순히 필요한 DOM 속성을 업데이트합니다. React는 전체 그래프에 영향을 미치는 구조나 상태가 변경될 때만 개입합니다.

서비스를 중단하지 않는 마이그레이션

소스에서 차트까지의 데이터 경로

라이브러리 교체에 대해 이야기하기 전에, DataLens에서 차트 렌더링이 어떻게 구성되어 있는지 이해하는 것이 중요합니다 — 왜냐하면 바로 이 아키텍처가 마이그레이션 방법을 결정했기 때문입니다.

사용자가 차트를 열면(또는 대시보드에서 렌더링될 때), 데이터 요청이 Node.js 백엔드로 전송됩니다. Node.js 백엔드는 차례로 Python 서비스에서 원시 데이터를 가져옵니다. 하지만 데이터 자체만으로는 차트가 아닙니다. Node에서 준비가 이루어집니다: 시각화 유형이 결정되고, 해당 유형에 대한 데이터 제한이 초과되지 않았는지 확인되고, 모든 것이 괜찮으면 특정 차트 유형에 특화된 데이터 준비 함수가 선택됩니다.

핵심 사항: 각 시각화 유형에는 자체 데이터 준비 함수가 있습니다. 선형 차트에는 하나, 막대 차트에는 다른 것, area에는 또 다른 것. 이 함수의 결과가 클라이언트로 전송되는 설정입니다. 클라이언트에서는 @gravity‑ui/charts로 직접 가지 않고 @gravity‑ui/chartkit으로 갑니다 — 여러 시각화 라이브러리와 동시에 작업하기 위해 사용하는 별도 패키지입니다. 특정 차트 유형에 필요한 라이브러리만 동적으로 로드하고 모든 라이브러리에 통합 인터페이스를 제공합니다 — 클라이언트 코드는 특정 유형을 렌더링하는 라이브러리가 무엇인지 알지도 생각하지도 않습니다.

라우팅 외에도 chartkit에는 라이브러리 자체 위에 래퍼가 포함됩니다: 특정 라이브러리의 범위에 맞지 않지만 DataLens에 필요한 로직. 예를 들어, 모바일 장치에서 탭 시 툴팁 표시 — 이 동작은 Highcharts와 @gravity‑ui/charts 모두에 동일하게 필요하므로 chartkit 수준에서 구현됩니다.

이런 기능들은 DataLens에서만 유용한 것이 아닐 수 있습니다. 그래서 @gravity‑ui/chartkit에 대한 자세한 설명은 별도의 글을 쓸 가치가 있습니다.

점진적 전환 전략

이 아키텍처 덕분에 저희는 모든 것을 한 번에 다시 쓰지 않고 단계적으로 마이그레이션할 수 있었습니다.

시각화 유형 수준에서 기능 플래그를 도입했습니다. 로직은 간단합니다: 특정 시각화에 대한 플래그가 true로 설정되면 — Node는 @gravity‑ui/charts 형식으로 데이터를 준비하고 클라이언트는 새 라이브러리를 사용합니다. 플래그가 설정되지 않으면 — 데이터는 Highcharts 형식으로 준비되고 이전 코드가 렌더링됩니다.

이것은 어떤 시점에 DataLens에서 두 라이브러리가 동시에 작동했다는 것을 의미합니다 — 각각 자체 차트 유형 세트에 대해. 이 접근법은 몇 가지 이점을 제공했습니다:

  • 격리된 위험. 새 선형 차트 구현의 버그가 여전히 Highcharts에 있는 막대 차트에 영향을 미치지 않습니다.

  • 점진적 검증. 전환 후 각 유형은 다음 유형이 마이그레이션하기 전에 프로덕션에서 관찰 기간을 거칩니다.

  • 롤백 가능성. 무언가 잘못되면 플래그를 끄는 것으로 충분합니다.

마이그레이션은 세 차례에 걸쳐 진행되었으며, 순서는 의식적으로 선택되었습니다 — 단순한 것에서 복잡한 것으로.

1차: pie와 treemap. 가장 논리적인 시작 — 축이 없는 시각화. 척도 없음, 크로스헤어 없음, 복잡한 좌표 시스템 없음. 이것은 가장 부하가 많은 유형에 위험을 감수하지 않고 기본 통합을 연습하고, 데이터 준비 파이프라인을 설정하고, 전환 인프라가 올바르게 작동하는지 확인할 수 있게 했습니다.

2차: bar‑y, bar‑y normalized, scatter. 다음 단계 — 축이 있는 차트이지만 중요한 제한이 있습니다: 이 유형들은 split 모드(여러 차트가 공통 X 축으로 위아래로 정렬되는)에서 사용되지 않고 혼합 차트에도 참여하지 않습니다. 이것은 엣지 케이스의 수를 크게 줄이고 전환을 예측 가능하게 만들었습니다.

3차: area, area normalized, bar‑x, bar‑x normalized, line 및 조합. 가장 복잡한 단계. 바로 이 유형들이 DataLens에서 가장 인기 있습니다: 대부분의 대시보드에서 사용됩니다.

게다가 여기서는 혼합 차트(예: 하나의 차트에 line과 bar‑x), split 모드 및 모든 비자명 상호작용 시나리오가 등장합니다. 이 시각화들 각각은 테스트 시 특별한 주의가 필요했으며, 기능 플래그는 프로덕션에서 무언가 잘못될 경우 롤백할 수 있는 기능을 제공했습니다.

기술적 해결책

설정 객체: 익숙하지만 더 나은. 저희는 의식적으로 차트 설명 방법으로 설정 객체를 선택했습니다. DataLens는 이미 이 접근법으로 작업했으며, 패러다임의 급격한 변화는 불필요한 장벽을 만들었을 것입니다. 설정 구조는 개발자들이 익숙한 것과 많이 겹칩니다: 시리즈, 축, 제목, 툴팁 — 모두 제자리에. 하지만 Highcharts의 인터페이스가 저희에게 실패한 것처럼 보였던 곳에서는 자체적인 결정을 내렸습니다. 독창성을 위한 것이 아니라 실제 사용에서 구체적인 문제나 불편함을 봤기 때문입니다.

최소 예시 — 시간 축이 있는 선형 차트:

import {Chart} from '@gravity-ui/charts';

<Chart
    data={{
        series: {
            data: [
                {
                    type: 'line',
                    name: '매출',
                    data: [
                        {x: new Date('2024-01-01').getTime(), y: 120},
                        {x: new Date('2024-02-01').getTime(), y: 145},
                        {x: new Date('2024-03-01').getTime(), y: 132},
                        {x: new Date('2024-04-01').getTime(), y: 178},
                    ],
                },
            ],
        },
        xAxis: {type: 'datetime'},
        yAxis: [{title: {text: ' '}}],
    }}
/>

결과는 다음과 같습니다:

하지만 더 복잡한 선형 차트를 만들 수도 있습니다:

합리적인 기본값과 필요한 곳에서의 커스터마이즈. 수년간의 BI 도구 개발을 통해 저희는 그런 제품에서 차트 라이브러리가 어떻게 동작해야 하는지에 대한 이해를 형성했습니다. 일반적인 시나리오는 설정 없이 상자에서 꺼내자마자 동작해야 합니다 — 놀라움 없이. 기본값이 충분하지 않은 곳에서는 명시적인 커스터마이즈 지점이 있습니다: 자체 React 컴포넌트를 툴팁 렌더러로 전달하고, 축 레이블 포매터를 오버라이드하고, 선 너비를 설정합니다. 이 모든 것은 라이브러리 내부를 들여다보지 않고 같은 설정 객체를 통해 이루어집니다.

Gravity UI와의 기본 통합. DataLens는 컴포넌트, 아이콘, CSS 테마를 갖춘 디자인 시스템인 Gravity UI를 기반으로 구축됩니다. 테마는 @gravity‑ui/uikit의 CSS 변수를 통해 작동합니다: 테마를 연결하면 모든 축, 그리드, 레이블 색상이 자동으로 밝거나 어두운 모드에 맞게 조정됩니다. 차트 인터페이스의 툴팁과 버튼은 디자인 시스템에서 접근성, 키보드 내비게이션, 이벤트 처리를 상속하는 표준 uikit 컴포넌트입니다.

혼돈 없는 확장. 차트 유형이 많을 때 아키텍처가 결과를 결정하기 시작합니다. 새 유형 추가는 코어를 수정하거나 기존 작동 중인 것을 망가뜨려서는 안 됩니다. 저희는 각 유형에 대한 통합 구조를 통해 이를 해결했습니다: 자체 데이터 준비, 자체 렌더 컴포넌트, 자체 타입 — 모두 격리된 모듈에. 라이브러리 코어는 구체적인 구현이 아닌 공통 계약을 통해서만 유형의 존재를 알고 있습니다.

시각적 테스트. 시각화는 무엇보다도 사용자가 보는 것이므로, 단위 테스트만으로는 충분하지 않습니다. 각 차트 유형은 재현 가능성을 위해 Docker에서 실행되는 Playwright 스크린샷 테스트로 커버됩니다. 한 모듈의 변경이 다른 모듈의 렌더링을 조용히 망가뜨릴 수 없습니다: 실패한 스냅샷으로 바로 보입니다.

결과 및 결론

전환에는 시간이 걸렸고 상당한 투자가 필요했습니다. 하지만 결과는 단순히 “한 라이브러리를 다른 것으로 교체"한 것이 아닙니다. 저희가 얻은 것:

  • 코드에 대한 완전한 제어. 무언가 올바르게 작동하지 않을 때 — 어디든지 들어가 원인을 이해하고 수정할 수 있습니다. 블랙박스 없음, 남의 코드 위에 해결책 없음, 벤더의 수정을 기다리는 일 없음.

  • 오픈소스. 라이브러리는 DataLens 팀만이 아니라 모든 사람에게 사용 가능합니다. 이것은 외부 기여, 공개 이슈, 투명한 변경 이력을 의미합니다. 외부 사용자가 보고하는 문제는 모든 사람을 위한 제품 개선에 도움이 됩니다.

  • 통합된 시각적 언어. 차트는 써드파티 벤더의 삽입 요소가 아닌 DataLens 인터페이스의 유기적인 부분이 되었습니다. 테마, 타이포그래피, 대화형 컴포넌트 — 모두 하나의 디자인 시스템에서.

자체 차트 라이브러리를 작성하는 것은 가볍게 내릴 결정이 아닙니다. 이것은 방대한 작업량이며, 또 다른 제품을 유지, 문서화, 발전시켜야 하는 필요성입니다.

하지만 도구가 데이터 시각화를 중심으로 구축되고, 동작에 대한 비표준 요구사항이 있으며, 외관에 대한 완전한 제어와 디자인 시스템과의 깊은 통합을 원한다면 — 남의 라이브러리에 대한 벤더 종속은 어느 순간 천장이 됩니다. 저희는 그것에 부딪혔고 제거하기로 결정했습니다.

앞으로의 계획

라이브러리는 활발히 개발되고 있으며, 현재 작업 중인 몇 가지 방향이 있습니다.

Framework‑agnostic core. 현재 @gravity‑ui/charts는 React 라이브러리입니다. 저희는 코어 — 데이터 준비 로직, 척도 구성, 좌표 계산 — 를 어떤 프레임워크에도 의존하지 않는 별도 패키지로 분리할 계획입니다. 이것은 두 가지 길을 열어줍니다: React 없이 순수 JavaScript로 사용하거나, 핵심 로직을 복제하지 않고 Vue, Angular 또는 다른 프레임워크를 위한 얇은 래퍼를 작성할 수 있습니다.

범주형 축을 위한 범위 슬라이더. 범위 슬라이더 컴포넌트는 현재 숫자 및 시간 축과 함께 작동합니다 — 범위를 선택하고 필요한 데이터 구간을 “확대"할 수 있습니다. 하지만 범주형 축(예: 국가 또는 이름 목록)도 BI 분석에 못지않게 중요합니다. 저희는 슬라이더가 범주와도 작동할 수 있도록 개선하고 있습니다: 값의 부분집합을 선택하고 데이터 필터링으로 전달합니다.

기여자를 위한 문서. 현재 새로운 차트 유형을 추가하는 것은 라이브러리 아키텍처를 내부적으로 아는 사람에게는 이해하기 쉬운 과정입니다. 하지만 외부 기여자에게는 그렇게 명확하지 않습니다. 저희는 상세한 가이드를 만들고 싶습니다: 유형 모듈이란 무엇인지, 어떤 계약을 이행해야 하는지, 테스트 작성 방법, 새 시각화를 공통 시스템에 연결하는 방법. 목표는 코드베이스로 작업한 적이 없는 사람이 문서만으로 새 차트 유형을 독립적으로 추가할 수 있는 것입니다.


@gravity‑ui/charts 라이브러리는 MIT 라이선스 하에 공개되어 있습니다 — 프로젝트에서 사용해 보세요.

GitHub에서 별표를 주시면 감사하겠습니다 ⭐

교체할 것인가 남길 것인가: DataLens가 Highcharts에서 마이그레이션한 방법

Sign in to save this post