Split View: 웹 데이터 시각화 라이브러리 2026 — D3·Plot·Visx·Recharts·ECharts·Vega-Lite를 한 번에 비교한다 (심층 가이드)
웹 데이터 시각화 라이브러리 2026 — D3·Plot·Visx·Recharts·ECharts·Vega-Lite를 한 번에 비교한다 (심층 가이드)
프롤로그 — 차트 라이브러리는 왜 이렇게 많을까
처음 웹 차트를 처음 띄울 때 사람들이 한 번씩은 던지는 질문이 있다. "그래서 뭘 써야 돼요?" 답은 매번 같다. "뭘 그리고 싶은데요?"
이 질문이 짜증나 보여도, 그게 진짜 답이다. 2026년의 웹 차트 생태계는 한 차원으로 정리되지 않는다. 적어도 네 개의 축이 있다.
- 추상 수준 — 픽셀까지 손대고 싶은가, JSON 한 덩어리로 끝내고 싶은가.
- 프레임워크 친화도 — React인가, Vue/Svelte인가, 그냥 바닐라인가.
- 데이터 규모 — 점이 1,000개인가, 100만 개인가, 1억 개인가.
- 상호작용 깊이 — 정적 인포그래픽인가, 라이브 대시보드인가, 분석 UI인가.
이 네 축의 어디에 서느냐에 따라 같은 "막대 차트"도 D3로 짜면 80줄, Plot으로 짜면 3줄, Recharts로 짜면 12줄이 된다. 줄 수가 적은 게 좋은 게 아니다. 자유도가 적은 거다.
2026년 5월 기준, 풍경을 한 줄로:
- D3.js v7 — 여전히 모든 추상의 기반. npm 주간 다운로드 약 800만. 직접 쓰는 빈도는 줄었지만, 쓰는 모든 도구가 D3 위에 서 있다.
- Observable Plot 0.7 — D3 메인테이너들이 만든 "그래머 오브 그래픽스" 레이어. ggplot2의 정신을 자바스크립트로 옮겼다.
- Visx 3.x — Airbnb의 React + D3 프리미티브. 활동성은 줄었지만 살아있고, 컴포넌트 위주 React 개발자에게 여전히 최적.
- Recharts 2.15 — React 친화 기본값. 작은 대시보드의 가장 빠른 길.
- Apache ECharts 6 — 2025년 후반에 6 메이저가 떨어졌다. Canvas·SVG 듀얼 렌더러, 거대한 차트 카탈로그, 중국·일본 엔터프라이즈 표준.
- Vega-Lite 5.20 — JSON 한 덩어리 = 차트. 분석/리포트 자동 생성과 잘 맞는다.
- Plotly.js 2.35 — 과학·금융용 인터랙티브 차트. 3D·지도까지.
- Chart.js 4.5 — Canvas 기반, 가장 가볍게 시작하는 길.
- AntV G2/G6 5 — 알리바바의 차트(G2)와 그래프/네트워크(G6) 패밀리.
- Nivo 0.99 — React + D3 스타일링이 가장 예쁜 차트. 상업 사이트에 잘 어울린다.
- Deck.gl 9 / regl 2 — WebGL/WebGPU로 수백만 점 그리는 GPU 가속 시각화.
이 글은 이 풍경을 추상 사다리로 정리한다 — 가장 낮은 곳(D3)부터 가장 높은 곳(Vega-Lite·Superset/Metabase)까지. 그리고 같은 차트를 네 라이브러리로 짜본 다음, 언제 뭘 써야 하는지 결정 표를 남긴다.
1장 · 추상 사다리 — D3에서 Plot으로, Plot에서 래퍼로
차트 라이브러리를 고를 때 가장 먼저 봐야 하는 건 추상 수준이다. 같은 문제(데이터를 픽셀로 그리기)를 어느 층에서 푸느냐.
[ 가장 높음 ]
Superset / Metabase (SQL → 차트, 사용자 클릭으로 끝)
Vega-Lite (JSON 스펙) (선언형 그래머)
Recharts / Nivo / Chart.js (컴포넌트 기본값 차트)
Observable Plot (그래머 + JS API)
Visx / AntV G2 (D3 위 컴포넌트·언어 추상)
D3.js (스케일·축·셰이프·셀렉션)
Canvas 2D / SVG (브라우저 원시 API)
WebGL / WebGPU (GPU)
[ 가장 낮음 ]
이 사다리에서 오를수록 빠르게 시작하지만, 자유도가 좁아진다. 내려갈수록 모든 픽셀을 손에 쥐지만, 더 많은 줄을 쓴다.
실용 규칙 세 개:
- 자기가 만들 차트가 라이브러리 갤러리에 있는 모양이면 위쪽 한두 칸에서 시작한다. Recharts·Nivo·ECharts·Plot 정도면 80%의 대시보드가 끝난다.
- 갤러리에 없으면 한 칸 내려간다. "축이 두 개 있고, 위에 화살표가 떠다니고, 클릭하면 사이드 패널이 열린다" 같은 건 Plot이나 Visx로 짠다.
- 그것도 안 되면 D3까지 내려간다. Sankey·Force·Geo 같은 비표준 시각화나, "한 번에 50만 점 + 60fps 줌"처럼 성능이 극단인 경우.
남에게 보여줄 차트(블로그 도표·연구 리포트)는 Plot으로 80%, D3로 20% 처리한다. 제품 안의 대시보드는 Recharts·ECharts로 80%, Visx·D3로 20%.
2장 · D3.js — 모든 추상의 어머니
D3는 차트 라이브러리가 아니다. 정확히는, 데이터 → DOM 매핑 라이브러리이고, 차트는 그 응용 중 하나일 뿐이다.
세 가지 핵심 추상을 기억하면 D3가 단번에 잡힌다.
스케일(Scale)
데이터 도메인(예: 01,000,000)을 픽셀 범위(예: 0600)로 변환한다. 종류:
scaleLinear·scaleLog·scaleSqrt·scaleTime— 연속형scaleBand·scalePoint·scaleOrdinal— 이산형scaleSequential·scaleQuantize— 색
import * as d3 from 'd3'
const x = d3.scaleBand()
.domain(data.map(d => d.month))
.range([60, 740])
.padding(0.2)
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.revenue)])
.range([460, 20])
셀렉션(Selection)과 조인(Join)
DOM 요소를 데이터에 묶는다. d3.select · selectAll · .data() · .join().
const svg = d3.select('#chart').append('svg')
.attr('width', 800).attr('height', 480)
svg.selectAll('rect.bar')
.data(data)
.join('rect')
.attr('class', 'bar')
.attr('x', d => x(d.month))
.attr('y', d => y(d.revenue))
.attr('width', x.bandwidth())
.attr('height', d => 460 - y(d.revenue))
.attr('fill', '#4f46e5')
이게 D3 코드의 "심장"이다. 데이터가 바뀌면 .join() 한 줄이 enter/update/exit를 처리한다.
셰이프 제너레이터(Shape generator)
선·면·아크 path 문자열을 만든다. d3.line · d3.area · d3.arc · d3.pie. 모듈 단위로 임포트한다(트리셰이킹).
2026년에 D3를 직접 쓰는 경우
솔직히, 점점 줄고 있다. 위쪽 한두 칸이 너무 좋아졌다. 그래도 D3를 직접 만져야 하는 순간:
- 비표준 시각화 — Sankey, Chord, Sunburst, Voronoi, Force-directed, Hexbin.
- 인터랙션이 차트의 본질 — 줌·드래그·러버밴드·브러시가 차트의 절반 이상.
- 성능을 직접 다뤄야 함 — Canvas 직접 그리기, 가상 스크롤, requestAnimationFrame 조율.
- 차트 라이브러리를 만든다 — Visx·Plot·Recharts 모두 내부적으로 D3 모듈을 쓴다.
D3의 함정도 하나. 버전 6/7부터 ES 모듈로 갈라졌다. d3-scale · d3-shape · d3-array만 임포트하면 번들이 50KB대로 떨어진다. import * as d3 from 'd3'는 편하지만 250KB 가깝다.
3장 · Observable Plot — D3 위의 그래머 레이어
D3 메인테이너들이 만든 고수준 API. 한 줄 요약: "R의 ggplot2를 자바스크립트로."
ggplot2를 모른다면 "그래머 오브 그래픽스"가 뭔지부터. 차트를 **레이어(layer)·마크(mark)·스케일(scale)·인코딩(encoding)**으로 분해해서 조합한다. 막대도, 점도, 선도 다 "마크"라는 이름의 같은 추상이다.
같은 막대 차트를 D3 80줄 대신:
import * as Plot from '@observablehq/plot'
const chart = Plot.plot({
marginLeft: 60,
y: { grid: true, label: 'Revenue (USD)' },
x: { label: 'Month' },
marks: [
Plot.barY(data, { x: 'month', y: 'revenue', fill: '#4f46e5' }),
Plot.ruleY([0])
]
})
document.getElementById('chart').append(chart)
5줄이다. 그리고 이 5줄은 D3 위에 그대로 깔려 있어서, 필요하면 그 아래로 내려갈 수 있다. 격자·스케일·축·범례가 모두 합리적인 기본값으로 켜져 있고, Plot.plot 한 번에 인터랙션·면적·히트맵·박스플롯·지도까지 다 된다.
2026년의 Plot 위상
- 버전 0.7(2025년 후반). 1.0 전이지만 프로덕션에서 충분히 안정.
- Observable Framework(정적 사이트 → 데이터 페이지)와 함께 데이터 저널리즘·내부 대시보드의 표준에 가까워졌다.
- React/Vue/Svelte 어디서든 쓴다(반환이 DOM 노드라 그냥
appendChild). - 인터랙션은
Plot.crosshair·Plot.tooltip·Plot.pointer같은 인터랙션 마크로 처리.
Plot이 잘 안 맞는 경우
- 픽셀 단위로 다르게 그려야 하는 커스텀 시각화.
- 한 차트 안에 인터랙션이 매우 복잡한 경우(드래그 + 다중 선택 + 사이드 패널 동기).
- 100만 점 이상 — Canvas 모드(
Plot.dot+render: Plot.renderCanvas옵션)도 있지만, 본질은 SVG 기반.
한 줄: 블로그 도표·연구 리포트·데이터 저널리즘은 거의 다 Plot이면 끝난다.
4장 · Visx — Airbnb의 React + D3 프리미티브
Visx는 D3 모듈을 React 컴포넌트로 재포장한 것이다. 차트 라이브러리가 아니라 차트를 만드는 도구 모음.
@visx/scale— D3 스케일을 React 친화 인터페이스로.@visx/shape— Bar, Line, Area, Pie 등 path를 그리는 컴포넌트.@visx/axis·@visx/grid·@visx/legend.@visx/tooltip— 포지셔닝까지 처리한 툴팁 훅.@visx/responsive—ParentSize로 컨테이너 폭 자동 추적.
장점은 분명하다.
- 순수 React — 가상 DOM 안에서 끝까지 산다.
useEffect없이. - 트리셰이킹 — 필요한 것만 import.
- 타입스크립트 — 타입이 정확하다.
import { scaleBand, scaleLinear } from '@visx/scale'
import { Bar } from '@visx/shape'
import { Group } from '@visx/group'
import { AxisBottom, AxisLeft } from '@visx/axis'
function RevenueChart({ data, width = 800, height = 480 }) {
const xScale = scaleBand({
domain: data.map(d => d.month),
range: [60, width - 20],
padding: 0.2,
})
const yScale = scaleLinear({
domain: [0, Math.max(...data.map(d => d.revenue))],
range: [height - 30, 20],
})
return (
<svg width={width} height={height}>
<Group>
{data.map(d => (
<Bar
key={d.month}
x={xScale(d.month)}
y={yScale(d.revenue)}
width={xScale.bandwidth()}
height={(height - 30) - yScale(d.revenue)}
fill="#4f46e5"
/>
))}
</Group>
<AxisLeft scale={yScale} left={60} />
<AxisBottom scale={xScale} top={height - 30} />
</svg>
)
}
2026년의 Visx 위상
솔직히 한마디 해야 한다. 활동성은 둔화됐다. 메이저 릴리스가 뜸하고, 이슈 처리 속도도 떨어졌다. 그래도:
- 아직 유지보수는 살아있다(2025년 후반에도 패치 릴리스가 나왔다).
- React로 D3 프리미티브가 필요한 사람에게는 여전히 1순위.
- Airbnb 내부에서 쓰니까 사라지진 않는다.
한 줄: "Recharts/Nivo가 너무 답답하고, D3로 직접 가긴 싫다"의 정확한 자리.
5장 · Recharts — React 친화 기본값의 왕
Recharts는 **"빠르게 시작하고 충분히 멀리 간다"**의 대명사다. 2026년 5월 기준 v2.15. v3 베타가 있긴 한데 프로덕션은 2.15가 표준.
같은 막대 차트:
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
function RevenueChart({ data }) {
return (
<ResponsiveContainer width="100%" height={480}>
<BarChart data={data}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Bar dataKey="revenue" fill="#4f46e5" />
</BarChart>
</ResponsiveContainer>
)
}
12줄. 반응형·툴팁·축·격자가 다 켜져 있다. 80%의 SaaS 대시보드는 여기서 끝.
Recharts가 정말 강한 지점
- React 컴포넌트로 깔끔하게 모인다 — 상태가 React로 들어오고 나간다.
- 합성(composition) —
Bar+Line+Area를 한 차트에 섞는 게 자연스럽다. - 반응형 —
ResponsiveContainer한 줄. - TypeScript 타입 정의가 합리적.
Recharts의 약점
- SVG 기반 — 5천 개 이상이면 살짝 끈적해지고, 1만 개 이상은 무겁다.
- 고급 인터랙션은 한계 — 줌·브러시·다중 선택을 깊게 들어가면 Plot/Visx로 가야 한다.
- 시각적으로 평범 — 디자인 톤은 디자이너의 손이 필요하다.
한 줄: 사내 대시보드·BI 위젯·블로그 차트라면 Recharts부터 시작하라. 거기서 안 풀리면 한 칸 내려간다.
6장 · Apache ECharts 6 — 엔터프라이즈의 야수
2025년 11월에 떨어진 ECharts 6은 단순한 차트 라이브러리가 아니라 시각화 플랫폼에 가깝다. Apache 재단 정식 프로젝트.
특징을 빠르게:
- Canvas + SVG 듀얼 렌더러 — 같은 옵션 객체로 둘 다 그린다. 대용량은 Canvas, 인쇄/벡터는 SVG.
- 거대한 차트 카탈로그 — Bar/Line/Scatter는 기본, Sankey·Tree·Treemap·Sunburst·Funnel·Gauge·Radar·Parallel·BoxPlot·Heatmap·Calendar·Graph·Map(GeoJSON)·3D(echarts-gl)까지.
- 선언적 옵션 — JSON 한 덩어리로 차트가 끝난다(Vega-Lite와 비슷한 정신).
- 반응형 —
resize한 줄. - WebGL 백엔드 —
echarts-gl로 100만 점 산점도·3D 지도.
같은 막대 차트:
import * as echarts from 'echarts/core'
import { BarChart } from 'echarts/charts'
import { GridComponent, TooltipComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([BarChart, GridComponent, TooltipComponent, CanvasRenderer])
const chart = echarts.init(document.getElementById('chart'))
chart.setOption({
xAxis: { type: 'category', data: data.map(d => d.month) },
yAxis: { type: 'value' },
tooltip: { trigger: 'axis' },
series: [{ type: 'bar', data: data.map(d => d.revenue), itemStyle: { color: '#4f46e5' } }],
})
ECharts가 강한 이유
- 차트 종류 — Bar/Line만 그릴 거면 Recharts가 낫다. Sunburst·Sankey·Radar·Map·Calendar Heatmap이 한 라이브러리에 다 있는 곳은 ECharts가 거의 유일.
- 대용량 처리 — Canvas 기본 + Progressive Rendering으로 50만 점 산점도가 자연스럽게 돈다.
- 테마 시스템 — JSON 테마로 한 번에 톤 갈아끼우기.
- i18n — 처음부터 다국어 전제.
ECharts의 약점
- 번들 사이즈 — 풀 빌드는 1MB가 넘는다. 모듈 분리 import가 필수.
- React 친화도 — 직접 init/dispose를 손으로 관리해야 한다.
echarts-for-react같은 래퍼가 있긴 함. - API 표면이 거대 — 옵션 객체가 깊다. 옵션 한 줄 차이로 결과가 크게 달라진다.
한 줄: "차트 종류가 폭주하는 BI" 또는 "데이터가 많고 인터랙션이 풍부해야 하는 대시보드"라면 ECharts.
7장 · Vega-Lite — JSON 한 덩어리가 차트가 된다
워싱턴 대학에서 시작된 선언형 그래머. JSON 하나로 차트를 정의한다.
import { default as vegaEmbed } from 'vega-embed'
vegaEmbed('#chart', {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
data: { values: data },
mark: { type: 'bar', color: '#4f46e5' },
encoding: {
x: { field: 'month', type: 'ordinal' },
y: { field: 'revenue', type: 'quantitative' },
},
})
이게 끝이다. 그리고 이 JSON 스펙은 버전 관리 가능하고, 사람이 읽고 쓰기 좋고, 자동 생성하기도 쉽다.
어디서 빛나는가
- 분석 리포트 자동 생성 — LLM이 데이터 보고 JSON을 뱉으면 그게 곧 차트. Vega-Lite는 LLM 시대에 의외로 잘 맞는 포맷이다.
- 데이터 저널리즘 — 차트의 "스펙"을 코드와 분리해서 관리.
- Jupyter/Altair 연동 — 파이썬 Altair가 같은 스펙을 뱉는다. 노트북에서 그린 차트를 그대로 웹으로 이식.
약점
- 인터랙션 깊이 — 기본 selection 문법이 있지만, "차트 클릭하면 외부 React 상태가 바뀌어야 함" 같은 통합에는 손이 든다.
- 번들 — Vega + Vega-Lite + vega-embed면 가볍지 않다.
한 줄: 차트의 "데이터/규격"을 시스템적으로 다루고 싶다면 Vega-Lite.
8장 · 그 외 — Plotly, Chart.js, AntV, Nivo
빠르게 한 줄씩.
- Plotly.js 2.35 — 과학/금융 인터랙티브 차트의 클래식. 3D·Geo·Financial(OHLC, Candlestick)이 강하다. 번들이 무거운 게 단점(MathJax 빼고도 700KB+).
plotly.js-basic-dist로 부분 임포트가 답. - Chart.js 4.5 — Canvas 기반. 가볍고 친절하지만 차트 종류가 적다. 작은 프로젝트의 첫 라이브러리로 무난.
- AntV G2 5 / G6 5 — 알리바바의 차트 G2와 그래프/네트워크 G6. 그래프 시각화는 G6가 D3-Force보다 편하다. 중국·일본권에서 강세.
- Nivo 0.99 — React + D3로 만든, 가장 예쁜 기본값을 가진 차트. 상업 사이트·랜딩 페이지에 잘 어울린다. 단점: 번들이 크고, 커스터마이즈 깊이는 Recharts/Visx보다 좁다.
9장 · 같은 막대 차트를 네 라이브러리로 — 한눈에
월별 매출([{month: 'Jan', revenue: 12400}, ...])을 막대로 그린다. 같은 데이터, 네 가지 접근.
D3 (저수준)
import * as d3 from 'd3'
const width = 800, height = 480, margin = { top: 20, right: 20, bottom: 30, left: 60 }
const svg = d3.select('#chart').append('svg').attr('width', width).attr('height', height)
const x = d3.scaleBand().domain(data.map(d => d.month))
.range([margin.left, width - margin.right]).padding(0.2)
const y = d3.scaleLinear().domain([0, d3.max(data, d => d.revenue)]).nice()
.range([height - margin.bottom, margin.top])
svg.append('g').attr('transform', `translate(0,${height - margin.bottom})`).call(d3.axisBottom(x))
svg.append('g').attr('transform', `translate(${margin.left},0)`).call(d3.axisLeft(y))
svg.selectAll('rect.bar').data(data).join('rect')
.attr('class', 'bar')
.attr('x', d => x(d.month))
.attr('y', d => y(d.revenue))
.attr('width', x.bandwidth())
.attr('height', d => y(0) - y(d.revenue))
.attr('fill', '#4f46e5')
자유도 100. 줄 수 25. 인터랙션 따로 추가.
Observable Plot (그래머)
import * as Plot from '@observablehq/plot'
const chart = Plot.plot({
marginLeft: 60,
y: { grid: true, label: 'Revenue (USD)' },
marks: [
Plot.barY(data, { x: 'month', y: 'revenue', fill: '#4f46e5', tip: true }),
Plot.ruleY([0]),
],
})
document.getElementById('chart').append(chart)
자유도 70. 줄 수 8. 툴팁 한 옵션.
Recharts (React 컴포넌트)
import { BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer } from 'recharts'
function RevenueChart({ data }) {
return (
<ResponsiveContainer width="100%" height={480}>
<BarChart data={data} margin={{ top: 20, right: 20, bottom: 30, left: 60 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Bar dataKey="revenue" fill="#4f46e5" />
</BarChart>
</ResponsiveContainer>
)
}
자유도 60. 줄 수 12. React 합성 친화.
ECharts (선언형 옵션)
import * as echarts from 'echarts'
const chart = echarts.init(document.getElementById('chart'))
chart.setOption({
grid: { left: 60, right: 20, top: 20, bottom: 30 },
xAxis: { type: 'category', data: data.map(d => d.month) },
yAxis: { type: 'value', name: 'Revenue (USD)' },
tooltip: { trigger: 'axis' },
series: [{ type: 'bar', data: data.map(d => d.revenue), itemStyle: { color: '#4f46e5' } }],
})
window.addEventListener('resize', () => chart.resize())
자유도 80. 줄 수 11. 옵션 객체 한 덩어리.
줄 수가 다 비슷한데, 뭐가 다른가
줄 수만 보면 큰 차이가 없다. 진짜 차이는:
- D3 — 데이터가 바뀌었을 때, 마우스 오버 했을 때, 줌 했을 때, 모든 동작을 손으로 짠다.
- Plot — 한 차트 단위. 인터랙션은 옵션으로. 외부 상태와의 통합은 약함.
- Recharts — React 상태로 모든 게 흐른다.
state.month가 바뀌면 즉시 다시 그린다. - ECharts —
chart.setOption()한 번으로 갈아끼운다. React 안에선useEffect로 동기화 직접.
차트가 앱의 한 구성요소라면 Recharts/Visx, 데이터 → 그림이 본질이라면 Plot/ECharts/D3.
10장 · 대용량 데이터 — Canvas, WebGL, Deck.gl, regl
여기부터는 다른 게임이다. 점 1만 개까지는 SVG로도 된다. 10만 개부터는 Canvas. 100만 개부터는 WebGL.
왜 SVG가 느려지는가
SVG는 각 점이 DOM 노드다. 점 10만 개 = DOM 10만. 브라우저의 레이아웃·페인트 비용이 노드 수에 선형으로 따라온다. 5천~1만 노드 근방에서 인터랙션이 끈적해지기 시작하고, 10만에서는 페이지 자체가 무거워진다.
Canvas 2D — 첫 단계
Canvas는 한 노드(canvas 엘리먼트) 안에 픽셀을 그린다. 점이 100만 개여도 DOM은 한 개. 단점은 히트 테스트(어느 점에 마우스가 올라갔나)를 직접 짜야 한다는 것.
라이브러리 별 Canvas 모드:
- D3 —
canvas.getContext('2d')로 직접 그리기. D3 스케일·셰이프는 그대로 쓴다. - Plot —
Plot.dot(..., { render: Plot.renderCanvas })형식의 Canvas 마크. - Chart.js / ECharts — 기본이 Canvas.
- Recharts / Visx — 기본 SVG. Canvas 모드는 없거나 제한적.
WebGL — 두 번째 단계
GPU로 점을 그린다. 100만 점도 가볍게.
- regl 2.x — WebGL을 functional하게 감싼 얇은 레이어. D3·Plot과 함께 쓰면 "스케일은 JS, 그리기는 GPU"가 깔끔.
- Deck.gl 9 — Uber의 GPU 시각화 프레임워크. 지도(MapboxGL/MapLibre/Google Maps) 위 1억 점, 3D 빌딩, 히트맵, 트레이일. 데이터 시각화 + 지도가 가장 자연스러운 곳.
- PixiJS / Three.js + 인스턴싱 — 일반 시각화는 아니지만, 산점도 100만 점에 쓰는 경우가 있다.
WebGPU — 다음 단계
2026년 1월에 WebGPU가 모든 메이저 브라우저에서 Baseline이 됐다. 시각화 라이브러리들은 천천히 따라가는 중.
- Deck.gl은 9.x에서 WebGPU 렌더러를 실험적으로 제공.
- regl-gpu가 일부 프로젝트에서 쓰임.
- 본격 채택은 2026년 말~2027년 사이.
100만 점 산점도, 무엇으로 그리는가
선택 흐름:
- 지도 위라면 Deck.gl — 의심 없이.
- 지도가 아니고 산점만이라면 regl + D3 스케일 — 100~300줄로 다 짠다.
- 빠르게 시작하고 싶고 정해진 차트 패턴이라면 ECharts 6 + Progressive Rendering — 옵션 한 줄.
- Three.js를 이미 쓰고 있다면 InstancedMesh.
11장 · 결정 프레임워크 — 언제 무엇을 쓰는가
표 하나에 정리한다.
| 시나리오 | 1순위 | 2순위 | 메모 |
|---|---|---|---|
| React 앱의 사내 대시보드 (점 < 5,000) | Recharts | Nivo | 합성·반응형이 자연스럽다 |
| Vue/Svelte/바닐라 대시보드 | ECharts | Plot | 프레임워크 중립 |
| 블로그 도표·연구 리포트 | Plot | ECharts | 5줄로 끝, 인쇄 깔끔 |
| 분석/탐색 UI (인터랙션 깊음) | Visx | D3 직접 | React 합성 + 자유도 |
| BI 위젯, 차트 종류 폭주 | ECharts 6 | Plotly | Sankey/Sunburst/Calendar |
| Sankey·Force·Chord 등 비표준 | D3 직접 | AntV G6 | 라이브러리 갤러리에 없음 |
| 지도 위 점/히트맵 | Deck.gl | Mapbox GL | GPU 가속 + 지도 |
| 100만 점 산점도 | regl + D3 | ECharts (Canvas) | DOM 한 개로 GPU |
| 과학/금융 (3D, OHLC) | Plotly | ECharts | 도메인 특화 |
| JSON 스펙으로 자동 생성 | Vega-Lite | Plot | LLM 친화 |
| 그래프/네트워크 | AntV G6 | D3-Force | G6의 알고리즘 카탈로그 |
| 데모/포트폴리오 | Nivo | Plot | 보기 좋음 우선 |
| 시스템적 데이터 셀프서비스 | Superset/Metabase | Lightdash | 사용자 클릭 = 차트 |
12장 · 안티패턴 — 실무에서 자주 보는 사고
자주 보는 잘못 다섯 개.
- 5천 점 라이브 산점도를 Recharts(SVG)로 — Canvas/ECharts로 바꾸면 즉시 살아난다.
- Plotly 풀 빌드를 한 차트 그리려고 import —
plotly.js-basic-dist나 ECharts로 도망. - D3로 막대 차트를 80줄 짜기 — Plot/Recharts 5~12줄이면 끝나는 일.
- ECharts를 React에서 init·dispose 누락 — 메모리 리크.
useEffect정리 함수 필수. - 여러 차트 라이브러리를 한 페이지에서 섞기 — 번들이 폭주, 디자인 톤이 불일치. 하나만 골라 끝까지 간다.
13장 · BI 소비자 측 — Superset, Metabase, Lightdash
마지막 한 갈래. 차트를 직접 짜지 않는 길.
- Apache Superset — 차트 종류 60종+, ECharts 기반, 무료 셀프호스트. 단점: 운영이 무겁다.
- Metabase — 가장 친절한 셀프서비스 BI. 비기술 사용자가 쿼리 없이 차트를 만든다. 오픈코어 + 클라우드.
- Lightdash — dbt 위에 올린 BI. 메트릭 정의를 dbt에 두고 그걸 시각화. 데이터 엔지니어링 친화.
- Grafana — 시계열·관측성에 특화. 시각화 라이브러리이기도 하다(공개 패널).
데이터팀이 차트를 코드로 짜는 시대는 점점 끝나가고 있다. 개발자가 짤 차트는 줄어들고, BI 도구가 짤 차트는 늘어난다. 그게 2026년의 큰 그림이다.
에필로그 — 사다리의 어디에 설지
웹 데이터 시각화는 사다리다. 위쪽으로 갈수록 빠르지만 좁고, 아래로 갈수록 느리지만 자유롭다. 2026년의 풍경에서 기억할 한 줄.
- 위쪽 80% — Recharts(React), ECharts(범용), Plot(블로그/리포트)이면 거의 다 된다.
- 중간 15% — 인터랙션이 깊어지면 Visx, 차트가 비표준이면 D3.
- 아래쪽 5% — 100만 점·지도 위 시각화는 Deck.gl / regl / WebGL/WebGPU.
이 비율은 라이브러리 인기와도 거의 비례한다. 그리고 그 가운데, D3는 직접 보이지 않게 모든 것의 아래에 깔려 있다. 라이브러리 하나를 고를 때마다, 그 라이브러리가 D3 모듈을 어떻게 쓰는지를 한 번 들여다보는 것이 다음 차트 라이브러리를 고를 때의 안목이 된다.
12개 체크리스트
- 차트가 React 컴포넌트로 자연스럽게 합성되는가?
- 데이터가 5,000점을 넘어가면 SVG에서 Canvas/WebGL로 옮겼는가?
- ECharts/Plotly 같은 거대 라이브러리는 모듈 분리 import를 썼는가?
- 같은 데이터에 차트 라이브러리가 두 개 이상 섞여 있지 않은가?
- 반응형은 ResizeObserver(또는 라이브러리 기본값)로 잡혔는가?
- 색 팔레트가 색맹 친화(viridis/cividis 등)인가?
- 0부터 시작하지 않는 y축이 사용자에게 명시되는가?
- 툴팁/포커스는 키보드로도 접근 가능한가?
- 차트의 원본 데이터가 CSV/JSON으로 다운로드 가능한가?
- 인쇄/PDF에서 깨지지 않는 SVG 모드가 준비됐는가?
- 차트 라이브러리의 트리쉐이킹이 번들 분석기에서 확인됐는가?
- 다국어(라벨/날짜/숫자 포맷)가 처음부터 들어갔는가?
다음 글 예고
다음 글 후보: D3 + WebGL 산점도 100만 점 60fps 만들기, Vega-Lite로 LLM이 차트를 직접 그리게 하기, Observable Framework로 정적 데이터 페이지 워크플로.
"차트는 데이터의 마지막 한 마디다. 그 한 마디를 어느 사다리에서 외칠지가, 글의 어조를 결정한다."
— 웹 데이터 시각화 라이브러리 2026, 끝.
참고 / References
- D3.js — https://d3js.org/
- Observable Plot — https://observablehq.com/plot/
- Observable Framework — https://observablehq.com/framework/
- Visx (Airbnb) — https://airbnb.io/visx/
- Recharts — https://recharts.org/
- Apache ECharts — https://echarts.apache.org/
- Vega-Lite — https://vega.github.io/vega-lite/
- Plotly.js — https://plotly.com/javascript/
- Chart.js — https://www.chartjs.org/
- AntV G2 — https://g2.antv.antgroup.com/
- AntV G6 — https://g6.antv.antgroup.com/
- Nivo — https://nivo.rocks/
- Deck.gl — https://deck.gl/
- regl — https://github.com/regl-project/regl
- Apache Superset — https://superset.apache.org/
- Metabase — https://www.metabase.com/
- Lightdash — https://www.lightdash.com/
- Grafana — https://grafana.com/
- A Layered Grammar of Graphics (Hadley Wickham) — https://vita.had.co.nz/papers/layered-grammar.html
Web Data Visualization Libraries 2026 — D3, Plot, Visx, Recharts, ECharts, Vega-Lite Compared (Deep Dive)
Prologue — Why are there so many chart libraries?
Anyone shipping their first web chart eventually asks: "Which one should I use?" The answer is always the same: "What do you want to draw?"
It sounds dismissive, but it is the real answer. The 2026 chart ecosystem doesn't collapse to a single dimension. There are at least four axes.
- Abstraction level — do you want to touch every pixel, or hand over a JSON blob and be done?
- Framework affinity — React, Vue/Svelte, or plain vanilla?
- Data scale — 1,000 points, 1,000,000, or 100,000,000?
- Interaction depth — static infographic, live dashboard, or analytical UI?
Where you stand on those four axes turns the same "bar chart" into 80 lines of D3, 3 lines of Plot, or 12 lines of Recharts. Fewer lines is not better. It is less freedom.
As of May 2026, the landscape in one breath:
- D3.js v7 — still the base of every abstraction. About 8M weekly npm downloads. You write it directly less often, but every tool you use stands on it.
- Observable Plot 0.7 — the grammar-of-graphics layer from D3's maintainers. The spirit of ggplot2 ported to JavaScript.
- Visx 3.x — Airbnb's React + D3 primitives. Activity has cooled, but it's alive, and still the best fit for component-first React teams.
- Recharts 2.15 — the React-friendly default. The fastest path for small dashboards.
- Apache ECharts 6 — the v6 major dropped late 2025. Dual Canvas/SVG renderer, a huge chart catalog, the de facto standard in Chinese and Japanese enterprise.
- Vega-Lite 5.20 — one JSON blob is the chart. Pairs beautifully with auto-generated analytical reports.
- Plotly.js 2.35 — interactive scientific and financial charts. 3D and maps too.
- Chart.js 4.5 — Canvas-based, the lightest way to start.
- AntV G2 / G6 5 — Alibaba's chart (G2) and graph/network (G6) family.
- Nivo 0.99 — React + D3 with the prettiest defaults. Sits well on commercial sites.
- Deck.gl 9 / regl 2 — WebGL/WebGPU-powered visualization for millions of points.
This article walks that landscape as an abstraction ladder — from the lowest (D3) to the highest (Vega-Lite, Superset/Metabase). Then we write the same chart in four libraries and leave a decision table for what to pick when.
1. The abstraction ladder — D3 to Plot to wrappers
The first thing to look at when choosing a chart library is abstraction level. At which layer are you solving the same problem (turn data into pixels)?
[ highest ]
Superset / Metabase (SQL -> chart, the user clicks; done)
Vega-Lite (JSON spec) (declarative grammar)
Recharts / Nivo / Chart.js (component defaults)
Observable Plot (grammar + JS API)
Visx / AntV G2 (D3-on-top component / language abstractions)
D3.js (scales, axes, shapes, selections)
Canvas 2D / SVG (browser primitives)
WebGL / WebGPU (GPU)
[ lowest ]
The higher you climb, the faster you start, but the narrower the freedom. The lower you go, the more pixels you grab — and the more lines you write.
Three practical rules:
- If the chart you need looks like something in a library's gallery, start one or two rungs up. Recharts / Nivo / ECharts / Plot covers 80% of dashboards.
- If it's not in any gallery, drop one rung. "Two axes, an arrow floating on top, click opens a side panel" — that's Plot or Visx territory.
- If that still won't fly, drop to D3. Sankey / Force / Geo and similar non-standard shapes, or extreme performance ("500k points zooming at 60fps").
For pieces meant for an audience (blog diagrams, research reports), do 80% with Plot, 20% with D3. For dashboards inside a product, do 80% with Recharts / ECharts, 20% with Visx / D3.
2. D3.js — the mother of every abstraction
D3 is not a chart library. Precisely, it is a data-to-DOM mapping library, and charts are just one application of it.
Three core abstractions make D3 click.
Scale
Maps a data domain (e.g. 0–1,000,000) to a pixel range (e.g. 0–600). Variants:
scaleLinear,scaleLog,scaleSqrt,scaleTime— continuousscaleBand,scalePoint,scaleOrdinal— discretescaleSequential,scaleQuantize— color
import * as d3 from 'd3'
const x = d3.scaleBand()
.domain(data.map(d => d.month))
.range([60, 740])
.padding(0.2)
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.revenue)])
.range([460, 20])
Selection and join
Bind DOM elements to data. d3.select, selectAll, .data(), .join().
const svg = d3.select('#chart').append('svg')
.attr('width', 800).attr('height', 480)
svg.selectAll('rect.bar')
.data(data)
.join('rect')
.attr('class', 'bar')
.attr('x', d => x(d.month))
.attr('y', d => y(d.revenue))
.attr('width', x.bandwidth())
.attr('height', d => 460 - y(d.revenue))
.attr('fill', '#4f46e5')
This is the "heart" of D3 code. When data changes, a single .join() handles enter / update / exit.
Shape generator
Produces line / area / arc path strings. d3.line, d3.area, d3.arc, d3.pie. Import per module (tree-shaking).
When you still reach for D3 directly in 2026
Honestly, less and less. The rungs above have gotten too good. Still, D3 directly when:
- Non-standard shapes — Sankey, Chord, Sunburst, Voronoi, Force-directed, Hexbin.
- Interaction is the essence of the chart — zoom, drag, rubber-band, brush make up more than half of the chart.
- You need to control performance — drawing to Canvas directly, virtual scroll, requestAnimationFrame choreography.
- You're building a chart library — Visx, Plot, and Recharts all use D3 modules internally.
One D3 trap. Since v6/7 the modules are split. Importing only d3-scale, d3-shape, d3-array keeps the bundle in the ~50KB range. import * as d3 from 'd3' is convenient but pulls close to 250KB.
3. Observable Plot — the grammar layer on top of D3
The high-level API built by D3's maintainers. One-line summary: "ggplot2 in JavaScript."
If you don't know ggplot2, the idea of a "grammar of graphics" is to decompose a chart into layers, marks, scales, encodings and compose them. Bars, points, lines are all the same abstraction called a "mark".
The same bar chart that took 25 lines of D3 becomes:
import * as Plot from '@observablehq/plot'
const chart = Plot.plot({
marginLeft: 60,
y: { grid: true, label: 'Revenue (USD)' },
x: { label: 'Month' },
marks: [
Plot.barY(data, { x: 'month', y: 'revenue', fill: '#4f46e5' }),
Plot.ruleY([0])
]
})
document.getElementById('chart').append(chart)
Five lines. And those five lines sit directly on D3, so when you need to, you can drop a rung. Grid, scales, axes, and legends come with reasonable defaults, and Plot.plot handles interactions, areas, heatmaps, boxplots, and maps in one place.
Where Plot stands in 2026
- Version 0.7 (late 2025). Pre-1.0, but production-stable.
- Paired with Observable Framework (static-site data pages), it has become close to the standard for data journalism and internal dashboards.
- Works inside React / Vue / Svelte (it returns a DOM node — just
appendChild). - Interactions are handled by interaction marks like
Plot.crosshair,Plot.tooltip,Plot.pointer.
Where Plot doesn't fit
- Custom visualizations that need pixel-level control.
- Single charts with heavy interactions (drag + multi-select + side-panel sync).
- More than a million points — there's a Canvas render mode (
Plot.dotwith therender: Plot.renderCanvasoption), but the core is SVG.
One line: for blog diagrams, research reports, and data journalism, Plot is almost always enough.
4. Visx — Airbnb's React + D3 primitives
Visx re-wraps D3 modules as React components. Not a chart library, a toolkit for building charts.
@visx/scale— D3 scales with a React-friendly interface.@visx/shape— Bar, Line, Area, Pie path components.@visx/axis,@visx/grid,@visx/legend.@visx/tooltip— a tooltip hook with positioning baked in.@visx/responsive—ParentSizefor tracking container width.
The strengths are clear.
- Pure React — everything lives inside the virtual DOM. No
useEffectescape hatch. - Tree-shaking — import only what you need.
- TypeScript — types are accurate.
import { scaleBand, scaleLinear } from '@visx/scale'
import { Bar } from '@visx/shape'
import { Group } from '@visx/group'
import { AxisBottom, AxisLeft } from '@visx/axis'
function RevenueChart({ data, width = 800, height = 480 }) {
const xScale = scaleBand({
domain: data.map(d => d.month),
range: [60, width - 20],
padding: 0.2,
})
const yScale = scaleLinear({
domain: [0, Math.max(...data.map(d => d.revenue))],
range: [height - 30, 20],
})
return (
<svg width={width} height={height}>
<Group>
{data.map(d => (
<Bar
key={d.month}
x={xScale(d.month)}
y={yScale(d.revenue)}
width={xScale.bandwidth()}
height={(height - 30) - yScale(d.revenue)}
fill="#4f46e5"
/>
))}
</Group>
<AxisLeft scale={yScale} left={60} />
<AxisBottom scale={xScale} top={height - 30} />
</svg>
)
}
Where Visx stands in 2026
Honestly, the activity has slowed. Majors come less often, issue throughput has dropped. Still:
- Maintenance is alive (patch releases shipped in late 2025).
- For React devs who need D3 primitives, it remains the first pick.
- Airbnb uses it internally, so it won't disappear.
One line: the exact spot where "Recharts / Nivo feel too rigid, but I'm not going down to raw D3" lands.
5. Recharts — king of React-friendly defaults
Recharts is the poster child of "start fast, go far enough". As of May 2026 it's at v2.15. There's a v3 beta around but 2.15 is the production standard.
The same bar chart:
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'
function RevenueChart({ data }) {
return (
<ResponsiveContainer width="100%" height={480}>
<BarChart data={data}>
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Bar dataKey="revenue" fill="#4f46e5" />
</BarChart>
</ResponsiveContainer>
)
}
Twelve lines. Responsive, tooltip, axes, grid — all on by default. 80% of SaaS dashboards stop here.
Where Recharts really shines
- Composes cleanly as React components — state flows in and out of React.
- Composition — mixing
Bar+Line+Areain one chart feels natural. - Responsive — one
ResponsiveContainerline. - TypeScript types are sensible.
Recharts weaknesses
- SVG-based — past 5,000 nodes it gets sluggish; past 10,000 it's heavy.
- Advanced interactions cap out — deep zoom / brush / multi-select push you toward Plot or Visx.
- Visually plain — design tone needs a designer's pass.
One line: for internal dashboards, BI widgets, and blog charts, start with Recharts. If it stops working, drop a rung.
6. Apache ECharts 6 — the enterprise beast
The ECharts 6 that landed in November 2025 is closer to a visualization platform than a chart library. An official Apache Foundation project.
Quick characteristics:
- Dual Canvas + SVG renderer — same option object draws both. Canvas for big data, SVG for print / vector.
- Huge chart catalog — Bar/Line/Scatter is the baseline; Sankey, Tree, Treemap, Sunburst, Funnel, Gauge, Radar, Parallel, BoxPlot, Heatmap, Calendar, Graph, Map (GeoJSON), 3D (
echarts-gl) — all in. - Declarative options — one JSON blob is the chart (spirit similar to Vega-Lite).
- Responsive — one
resizecall. - WebGL backend —
echarts-glfor million-point scatter / 3D maps.
Same bar chart:
import * as echarts from 'echarts/core'
import { BarChart } from 'echarts/charts'
import { GridComponent, TooltipComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([BarChart, GridComponent, TooltipComponent, CanvasRenderer])
const chart = echarts.init(document.getElementById('chart'))
chart.setOption({
xAxis: { type: 'category', data: data.map(d => d.month) },
yAxis: { type: 'value' },
tooltip: { trigger: 'axis' },
series: [{ type: 'bar', data: data.map(d => d.revenue), itemStyle: { color: '#4f46e5' } }],
})
Why ECharts is strong
- Chart variety — for Bar/Line only, Recharts is nicer. ECharts is almost the only single library where Sunburst, Sankey, Radar, Map, and Calendar Heatmap all live together.
- Big-data handling — Canvas baseline plus progressive rendering means 500k-point scatter is natural.
- Theme system — swap the entire tone via a JSON theme.
- i18n — multilingual was a premise from day one.
ECharts weaknesses
- Bundle size — full build is over 1MB. Modular imports are mandatory.
- React affinity — you manage init / dispose by hand. Wrappers like
echarts-for-reactexist. - Huge API surface — option objects are deep. A single option line can swing the output dramatically.
One line: for "a BI with a wild chart variety" or "a heavy-interaction dashboard with lots of data", reach for ECharts.
7. Vega-Lite — JSON blob becomes a chart
A declarative grammar out of the University of Washington. One JSON object defines a chart.
import { default as vegaEmbed } from 'vega-embed'
vegaEmbed('#chart', {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
data: { values: data },
mark: { type: 'bar', color: '#4f46e5' },
encoding: {
x: { field: 'month', type: 'ordinal' },
y: { field: 'revenue', type: 'quantitative' },
},
})
That's it. And that JSON spec is versionable, easy for humans to read and write, and trivial to auto-generate.
Where it shines
- Auto-generated analytical reports — let an LLM look at the data and emit a JSON spec; that spec is the chart. Vega-Lite turns out to be unexpectedly well-fitted to the LLM era.
- Data journalism — keeps the chart "spec" separate from code.
- Jupyter / Altair pairing — Python's Altair emits the same spec. Charts drawn in a notebook port straight to the web.
Weaknesses
- Interaction depth — basic
selectionsyntax exists, but integrations like "clicking a chart updates external React state" need real work. - Bundle — Vega + Vega-Lite + vega-embed isn't featherweight.
One line: if you want to treat the chart's data / spec systemically, pick Vega-Lite.
8. The rest — Plotly, Chart.js, AntV, Nivo
One line each.
- Plotly.js 2.35 — the classic for science / finance interactive charts. Strong 3D, geo, financial (OHLC, candlestick). Heavy bundle (700KB+ even without MathJax).
plotly.js-basic-distfor partial imports is the answer. - Chart.js 4.5 — Canvas-based, light and friendly, chart variety is narrow. Fine first library for small projects.
- AntV G2 5 / G6 5 — Alibaba's chart (G2) and graph/network (G6). Graph viz with G6 is easier than D3-Force. Strong in China and Japan.
- Nivo 0.99 — React + D3 with the prettiest defaults. Pairs with commercial sites and landing pages. Downsides: bundle is large, customization depth is narrower than Recharts / Visx.
9. The same bar chart in four libraries — at a glance
Monthly revenue ([{month: 'Jan', revenue: 12400}, ...]) as bars. Same data, four approaches.
D3 (low level)
import * as d3 from 'd3'
const width = 800, height = 480, margin = { top: 20, right: 20, bottom: 30, left: 60 }
const svg = d3.select('#chart').append('svg').attr('width', width).attr('height', height)
const x = d3.scaleBand().domain(data.map(d => d.month))
.range([margin.left, width - margin.right]).padding(0.2)
const y = d3.scaleLinear().domain([0, d3.max(data, d => d.revenue)]).nice()
.range([height - margin.bottom, margin.top])
svg.append('g').attr('transform', `translate(0,${height - margin.bottom})`).call(d3.axisBottom(x))
svg.append('g').attr('transform', `translate(${margin.left},0)`).call(d3.axisLeft(y))
svg.selectAll('rect.bar').data(data).join('rect')
.attr('class', 'bar')
.attr('x', d => x(d.month))
.attr('y', d => y(d.revenue))
.attr('width', x.bandwidth())
.attr('height', d => y(0) - y(d.revenue))
.attr('fill', '#4f46e5')
Freedom 100. Lines 25. Interactions added separately.
Observable Plot (grammar)
import * as Plot from '@observablehq/plot'
const chart = Plot.plot({
marginLeft: 60,
y: { grid: true, label: 'Revenue (USD)' },
marks: [
Plot.barY(data, { x: 'month', y: 'revenue', fill: '#4f46e5', tip: true }),
Plot.ruleY([0]),
],
})
document.getElementById('chart').append(chart)
Freedom 70. Lines 8. Tooltip in one option.
Recharts (React components)
import { BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer } from 'recharts'
function RevenueChart({ data }) {
return (
<ResponsiveContainer width="100%" height={480}>
<BarChart data={data} margin={{ top: 20, right: 20, bottom: 30, left: 60 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Bar dataKey="revenue" fill="#4f46e5" />
</BarChart>
</ResponsiveContainer>
)
}
Freedom 60. Lines 12. React composition friendly.
ECharts (declarative options)
import * as echarts from 'echarts'
const chart = echarts.init(document.getElementById('chart'))
chart.setOption({
grid: { left: 60, right: 20, top: 20, bottom: 30 },
xAxis: { type: 'category', data: data.map(d => d.month) },
yAxis: { type: 'value', name: 'Revenue (USD)' },
tooltip: { trigger: 'axis' },
series: [{ type: 'bar', data: data.map(d => d.revenue), itemStyle: { color: '#4f46e5' } }],
})
window.addEventListener('resize', () => chart.resize())
Freedom 80. Lines 11. One option blob.
Line counts look similar — what really differs?
Looking only at line counts hides the real distance. The real differences:
- D3 — when data changes, when the mouse hovers, when you zoom, you write every behaviour by hand.
- Plot — chart-shaped unit. Interactions via options. Integrating with external state is weak.
- Recharts — everything flows through React state.
state.monthflips and the chart redraws immediately. - ECharts —
chart.setOption()swaps the whole thing. Inside React you synchronize viauseEffectyourself.
If the chart is one piece of the app, lean Recharts / Visx. If data turning into a picture is the essence, lean Plot / ECharts / D3.
10. Big data — Canvas, WebGL, Deck.gl, regl
This is a different game. Up to ~10k points SVG holds. Past 100k, go Canvas. Past 1M, go WebGL.
Why SVG slows down
Each SVG point is a DOM node. 100,000 points = 100,000 nodes. Browser layout / paint cost scales linearly with node count. Past 5k–10k nodes interactions go sticky, past 100k the page itself is heavy.
Canvas 2D — first step
Canvas paints pixels inside one node (the canvas element). One DOM node for a million points. The catch: you write hit-testing (which point is under the mouse) by hand.
Canvas modes per library:
- D3 — draw directly via
canvas.getContext('2d'). D3 scales and shapes still apply. - Plot — Canvas marks like
Plot.dot(..., { render: Plot.renderCanvas }). - Chart.js / ECharts — Canvas by default.
- Recharts / Visx — SVG by default. Canvas mode is absent or limited.
WebGL — second step
GPU draws points. A million points is light.
- regl 2.x — a thin functional wrapper around WebGL. Pairs cleanly with D3 / Plot — "scales in JS, drawing on GPU".
- Deck.gl 9 — Uber's GPU visualization framework. 100M points on a map (Mapbox GL / MapLibre / Google Maps), 3D buildings, heatmaps, trails. The most natural home for data viz plus maps.
- PixiJS / Three.js + instancing — not general viz, but used for million-point scatter occasionally.
WebGPU — next step
January 2026 brought WebGPU to baseline across major browsers. Viz libraries follow slowly.
- Deck.gl ships an experimental WebGPU renderer in 9.x.
- regl-gpu sees use in some projects.
- Full-blown adoption likely late 2026 to 2027.
Million-point scatter — what do you use?
Decision flow:
- On a map? Deck.gl — no second thought.
- No map, just scatter? regl + D3 scales — 100–300 lines covers it.
- Want quick start with a familiar chart pattern? ECharts 6 + progressive rendering — one option line.
- Already on Three.js? InstancedMesh.
11. Decision framework — what to use when
One table.
| Scenario | First pick | Second pick | Note |
|---|---|---|---|
| React internal dashboard (less than 5,000 points) | Recharts | Nivo | Composition / responsive are natural |
| Vue / Svelte / vanilla dashboard | ECharts | Plot | Framework-neutral |
| Blog diagrams, research reports | Plot | ECharts | Five lines, prints cleanly |
| Analytical / exploratory UI | Visx | Raw D3 | React composition + freedom |
| BI widgets, wild chart variety | ECharts 6 | Plotly | Sankey / Sunburst / Calendar |
| Non-standard shapes (Sankey, Force, Chord) | Raw D3 | AntV G6 | Not in any gallery |
| Points / heatmap on a map | Deck.gl | Mapbox GL | GPU + maps |
| Million-point scatter | regl + D3 | ECharts (Canvas) | One DOM, GPU |
| Science / finance (3D, OHLC) | Plotly | ECharts | Domain-specific |
| Auto-generated from a JSON spec | Vega-Lite | Plot | LLM-friendly |
| Graph / network | AntV G6 | D3-Force | G6's algorithm catalog |
| Demo / portfolio | Nivo | Plot | Pretty-first |
| Self-service / systemic data | Superset / Metabase | Lightdash | Click = chart |
12. Anti-patterns — common mistakes in the wild
Five things you see repeatedly.
- Live 5,000-point scatter with Recharts (SVG) — switch to Canvas / ECharts; it springs back to life immediately.
- Importing the full Plotly build to draw one chart — flee to
plotly.js-basic-distor ECharts. - Writing 80 lines of D3 for a bar chart — Plot / Recharts ends it in 5–12 lines.
- Missing
dispose()for ECharts inside React — memory leak. TheuseEffectcleanup function is mandatory. - Mixing multiple chart libraries on one page — bundle explosion plus design-tone mismatch. Pick one and go all the way.
13. The BI consumer side — Superset, Metabase, Lightdash
One more branch. The path of not writing charts yourself.
- Apache Superset — 60+ chart types, ECharts-based, free self-hostable. Downside: heavy to operate.
- Metabase — the most user-friendly self-service BI. Non-technical users build charts without queries. Open-core plus cloud.
- Lightdash — BI on top of dbt. Metric definitions stay in dbt, the visualization layer sits on top. Data-engineering-friendly.
- Grafana — specialized for time-series / observability. It's also a viz library in itself (public panels).
The era where data teams write charts in code is gradually ending. The charts developers ship are shrinking; the charts BI tools ship are growing. That's the macro picture of 2026.
Epilogue — Which rung do you stand on?
Web data visualization is a ladder. The higher you climb, the faster but the narrower. The lower you go, the slower but the freer. One line to remember from the 2026 landscape.
- Top 80% — Recharts (React), ECharts (universal), Plot (blog / report) cover almost everything.
- Middle 15% — deep interactions push you to Visx; non-standard shapes to D3.
- Bottom 5% — million-point or map-overlay viz goes Deck.gl / regl / WebGL / WebGPU.
That ratio roughly tracks library popularity. And in the middle, D3 is invisibly underneath everything. Each time you pick a library, peek once at how that library uses D3 modules — that's how you build the taste for picking the next one.
12-item checklist
- Does the chart compose naturally as a React component?
- When data crosses 5,000 points, did you move from SVG to Canvas / WebGL?
- For giant libraries like ECharts / Plotly, are you using modular imports?
- Are two or more chart libraries on the same page?
- Is responsiveness handled (ResizeObserver or the library's default)?
- Is the color palette colorblind-friendly (viridis / cividis etc.)?
- Is a non-zero y-axis baseline made explicit to the user?
- Are tooltips / focus reachable via keyboard?
- Can the underlying data be downloaded as CSV / JSON?
- Is there a print / PDF-safe SVG mode?
- Did the bundle analyzer confirm tree-shaking for the chart library?
- Was i18n (labels, dates, number format) built in from the start?
Next post preview
Candidate next titles: "D3 + WebGL million-point scatter at 60fps", "Letting an LLM draw your charts with Vega-Lite", "Observable Framework as a static data-page workflow".
"A chart is the data's final word. Which rung of the ladder you shout it from sets the tone of the whole article."
— Web data visualization libraries 2026, end.
References
- D3.js — https://d3js.org/
- Observable Plot — https://observablehq.com/plot/
- Observable Framework — https://observablehq.com/framework/
- Visx (Airbnb) — https://airbnb.io/visx/
- Recharts — https://recharts.org/
- Apache ECharts — https://echarts.apache.org/
- Vega-Lite — https://vega.github.io/vega-lite/
- Plotly.js — https://plotly.com/javascript/
- Chart.js — https://www.chartjs.org/
- AntV G2 — https://g2.antv.antgroup.com/
- AntV G6 — https://g6.antv.antgroup.com/
- Nivo — https://nivo.rocks/
- Deck.gl — https://deck.gl/
- regl — https://github.com/regl-project/regl
- Apache Superset — https://superset.apache.org/
- Metabase — https://www.metabase.com/
- Lightdash — https://www.lightdash.com/
- Grafana — https://grafana.com/
- A Layered Grammar of Graphics (Hadley Wickham) — https://vita.had.co.nz/papers/layered-grammar.html