Skip to content

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)
[ 가장 낮음 ]

이 사다리에서 오를수록 빠르게 시작하지만, 자유도가 좁아진다. 내려갈수록 모든 픽셀을 손에 쥐지만, 더 많은 줄을 쓴다.

실용 규칙 세 개:

  1. 자기가 만들 차트가 라이브러리 갤러리에 있는 모양이면 위쪽 한두 칸에서 시작한다. Recharts·Nivo·ECharts·Plot 정도면 80%의 대시보드가 끝난다.
  2. 갤러리에 없으면 한 칸 내려간다. "축이 두 개 있고, 위에 화살표가 떠다니고, 클릭하면 사이드 패널이 열린다" 같은 건 Plot이나 Visx로 짠다.
  3. 그것도 안 되면 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/responsiveParentSize로 컨테이너 폭 자동 추적.

장점은 분명하다.

  • 순수 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가 바뀌면 즉시 다시 그린다.
  • EChartschart.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 모드:

  • D3canvas.getContext('2d')로 직접 그리기. D3 스케일·셰이프는 그대로 쓴다.
  • PlotPlot.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만 점 산점도, 무엇으로 그리는가

선택 흐름:

  1. 지도 위라면 Deck.gl — 의심 없이.
  2. 지도가 아니고 산점만이라면 regl + D3 스케일 — 100~300줄로 다 짠다.
  3. 빠르게 시작하고 싶고 정해진 차트 패턴이라면 ECharts 6 + Progressive Rendering — 옵션 한 줄.
  4. Three.js를 이미 쓰고 있다면 InstancedMesh.

11장 · 결정 프레임워크 — 언제 무엇을 쓰는가

표 하나에 정리한다.

시나리오1순위2순위메모
React 앱의 사내 대시보드 (점 < 5,000)RechartsNivo합성·반응형이 자연스럽다
Vue/Svelte/바닐라 대시보드EChartsPlot프레임워크 중립
블로그 도표·연구 리포트PlotECharts5줄로 끝, 인쇄 깔끔
분석/탐색 UI (인터랙션 깊음)VisxD3 직접React 합성 + 자유도
BI 위젯, 차트 종류 폭주ECharts 6PlotlySankey/Sunburst/Calendar
Sankey·Force·Chord 등 비표준D3 직접AntV G6라이브러리 갤러리에 없음
지도 위 점/히트맵Deck.glMapbox GLGPU 가속 + 지도
100만 점 산점도regl + D3ECharts (Canvas)DOM 한 개로 GPU
과학/금융 (3D, OHLC)PlotlyECharts도메인 특화
JSON 스펙으로 자동 생성Vega-LitePlotLLM 친화
그래프/네트워크AntV G6D3-ForceG6의 알고리즘 카탈로그
데모/포트폴리오NivoPlot보기 좋음 우선
시스템적 데이터 셀프서비스Superset/MetabaseLightdash사용자 클릭 = 차트

12장 · 안티패턴 — 실무에서 자주 보는 사고

자주 보는 잘못 다섯 개.

  1. 5천 점 라이브 산점도를 Recharts(SVG)로 — Canvas/ECharts로 바꾸면 즉시 살아난다.
  2. Plotly 풀 빌드를 한 차트 그리려고 importplotly.js-basic-dist나 ECharts로 도망.
  3. D3로 막대 차트를 80줄 짜기 — Plot/Recharts 5~12줄이면 끝나는 일.
  4. ECharts를 React에서 init·dispose 누락 — 메모리 리크. useEffect 정리 함수 필수.
  5. 여러 차트 라이브러리를 한 페이지에서 섞기 — 번들이 폭주, 디자인 톤이 불일치. 하나만 골라 끝까지 간다.

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개 체크리스트

  1. 차트가 React 컴포넌트로 자연스럽게 합성되는가?
  2. 데이터가 5,000점을 넘어가면 SVG에서 Canvas/WebGL로 옮겼는가?
  3. ECharts/Plotly 같은 거대 라이브러리는 모듈 분리 import를 썼는가?
  4. 같은 데이터에 차트 라이브러리가 두 개 이상 섞여 있지 않은가?
  5. 반응형은 ResizeObserver(또는 라이브러리 기본값)로 잡혔는가?
  6. 색 팔레트가 색맹 친화(viridis/cividis 등)인가?
  7. 0부터 시작하지 않는 y축이 사용자에게 명시되는가?
  8. 툴팁/포커스는 키보드로도 접근 가능한가?
  9. 차트의 원본 데이터가 CSV/JSON으로 다운로드 가능한가?
  10. 인쇄/PDF에서 깨지지 않는 SVG 모드가 준비됐는가?
  11. 차트 라이브러리의 트리쉐이킹이 번들 분석기에서 확인됐는가?
  12. 다국어(라벨/날짜/숫자 포맷)가 처음부터 들어갔는가?

다음 글 예고

다음 글 후보: D3 + WebGL 산점도 100만 점 60fps 만들기, Vega-Lite로 LLM이 차트를 직접 그리게 하기, Observable Framework로 정적 데이터 페이지 워크플로.

"차트는 데이터의 마지막 한 마디다. 그 한 마디를 어느 사다리에서 외칠지가, 글의 어조를 결정한다."

— 웹 데이터 시각화 라이브러리 2026, 끝.


참고 / References

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:

  1. 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.
  2. 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.
  3. 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 — continuous
  • scaleBand, scalePoint, scaleOrdinal — discrete
  • scaleSequential, 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.dot with the render: Plot.renderCanvas option), 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/responsiveParentSize for tracking container width.

The strengths are clear.

  • Pure React — everything lives inside the virtual DOM. No useEffect escape 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 + Area in one chart feels natural.
  • Responsive — one ResponsiveContainer line.
  • 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 resize call.
  • WebGL backendecharts-gl for 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-react exist.
  • 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 selection syntax 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-dist for 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.month flips and the chart redraws immediately.
  • EChartschart.setOption() swaps the whole thing. Inside React you synchronize via useEffect yourself.

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:

  1. On a map? Deck.gl — no second thought.
  2. No map, just scatter? regl + D3 scales — 100–300 lines covers it.
  3. Want quick start with a familiar chart pattern? ECharts 6 + progressive rendering — one option line.
  4. Already on Three.js? InstancedMesh.

11. Decision framework — what to use when

One table.

ScenarioFirst pickSecond pickNote
React internal dashboard (less than 5,000 points)RechartsNivoComposition / responsive are natural
Vue / Svelte / vanilla dashboardEChartsPlotFramework-neutral
Blog diagrams, research reportsPlotEChartsFive lines, prints cleanly
Analytical / exploratory UIVisxRaw D3React composition + freedom
BI widgets, wild chart varietyECharts 6PlotlySankey / Sunburst / Calendar
Non-standard shapes (Sankey, Force, Chord)Raw D3AntV G6Not in any gallery
Points / heatmap on a mapDeck.glMapbox GLGPU + maps
Million-point scatterregl + D3ECharts (Canvas)One DOM, GPU
Science / finance (3D, OHLC)PlotlyEChartsDomain-specific
Auto-generated from a JSON specVega-LitePlotLLM-friendly
Graph / networkAntV G6D3-ForceG6's algorithm catalog
Demo / portfolioNivoPlotPretty-first
Self-service / systemic dataSuperset / MetabaseLightdashClick = chart

12. Anti-patterns — common mistakes in the wild

Five things you see repeatedly.

  1. Live 5,000-point scatter with Recharts (SVG) — switch to Canvas / ECharts; it springs back to life immediately.
  2. Importing the full Plotly build to draw one chart — flee to plotly.js-basic-dist or ECharts.
  3. Writing 80 lines of D3 for a bar chart — Plot / Recharts ends it in 5–12 lines.
  4. Missing dispose() for ECharts inside React — memory leak. The useEffect cleanup function is mandatory.
  5. 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

  1. Does the chart compose naturally as a React component?
  2. When data crosses 5,000 points, did you move from SVG to Canvas / WebGL?
  3. For giant libraries like ECharts / Plotly, are you using modular imports?
  4. Are two or more chart libraries on the same page?
  5. Is responsiveness handled (ResizeObserver or the library's default)?
  6. Is the color palette colorblind-friendly (viridis / cividis etc.)?
  7. Is a non-zero y-axis baseline made explicit to the user?
  8. Are tooltips / focus reachable via keyboard?
  9. Can the underlying data be downloaded as CSV / JSON?
  10. Is there a print / PDF-safe SVG mode?
  11. Did the bundle analyzer confirm tree-shaking for the chart library?
  12. 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