LabHub

블로그

웹 데이터 시각화 라이브러리 2026 — D3·Plot·Visx·Recharts·ECharts·Vega-Lite를 한 번에 비교한다 (심층 가이드)

한국어English日本語

프롤로그 — 차트 라이브러리는 왜 이렇게 많을까

처음 웹 차트를 처음 띄울 때 사람들이 한 번씩은 던지는 질문이 있다. "그래서 뭘 써야 돼요?" 답은 매번 같다. "뭘 그리고 싶은데요?"

이 질문이 짜증나 보여도, 그게 진짜 답이다. 2026년의 웹 차트 생태계는 한 차원으로 정리되지 않는다. 적어도 네 개의 축이 있다.

이 네 축의 어디에 서느냐에 따라 같은 "막대 차트"도 D3로 짜면 80줄, Plot으로 짜면 3줄, Recharts로 짜면 12줄이 된다. 줄 수가 적은 게 좋은 게 아니다. 자유도가 적은 거다.

2026년 5월 기준, 풍경을 한 줄로:

이 글은 이 풍경을 추상 사다리로 정리한다 — 가장 낮은 곳(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)

데이터 도메인(예: 0~1,000,000)을 픽셀 범위(예: 0~600)로 변환한다. 종류:

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를 직접 만져야 하는 순간:

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 위상

Plot이 잘 안 맞는 경우

한 줄: 블로그 도표·연구 리포트·데이터 저널리즘은 거의 다 Plot이면 끝난다.


4장 · Visx — Airbnb의 React + D3 프리미티브

Visx는 D3 모듈을 React 컴포넌트로 재포장한 것이다. 차트 라이브러리가 아니라 차트를 만드는 도구 모음.

장점은 분명하다.

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 위상

솔직히 한마디 해야 한다. 활동성은 둔화됐다. 메이저 릴리스가 뜸하고, 이슈 처리 속도도 떨어졌다. 그래도:

한 줄: "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가 정말 강한 지점

Recharts의 약점

한 줄: 사내 대시보드·BI 위젯·블로그 차트라면 Recharts부터 시작하라. 거기서 안 풀리면 한 칸 내려간다.


6장 · Apache ECharts 6 — 엔터프라이즈의 야수

2025년 11월에 떨어진 ECharts 6은 단순한 차트 라이브러리가 아니라 시각화 플랫폼에 가깝다. Apache 재단 정식 프로젝트.

특징을 빠르게:

같은 막대 차트:

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가 강한 이유

ECharts의 약점

한 줄: "차트 종류가 폭주하는 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 스펙은 버전 관리 가능하고, 사람이 읽고 쓰기 좋고, 자동 생성하기도 쉽다.

어디서 빛나는가

약점

한 줄: 차트의 "데이터/규격"을 시스템적으로 다루고 싶다면 Vega-Lite.


8장 · 그 외 — Plotly, Chart.js, AntV, Nivo

빠르게 한 줄씩.


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. 옵션 객체 한 덩어리.

줄 수가 다 비슷한데, 뭐가 다른가

줄 수만 보면 큰 차이가 없다. 진짜 차이는:

차트가 앱의 한 구성요소라면 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 모드:

WebGL — 두 번째 단계

GPU로 점을 그린다. 100만 점도 가볍게.

WebGPU — 다음 단계

2026년 1월에 WebGPU가 모든 메이저 브라우저에서 Baseline이 됐다. 시각화 라이브러리들은 천천히 따라가는 중.

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

마지막 한 갈래. 차트를 직접 짜지 않는 길.

데이터팀이 차트를 코드로 짜는 시대는 점점 끝나가고 있다. 개발자가 짤 차트는 줄어들고, BI 도구가 짤 차트는 늘어난다. 그게 2026년의 큰 그림이다.


에필로그 — 사다리의 어디에 설지

웹 데이터 시각화는 사다리다. 위쪽으로 갈수록 빠르지만 좁고, 아래로 갈수록 느리지만 자유롭다. 2026년의 풍경에서 기억할 한 줄.

이 비율은 라이브러리 인기와도 거의 비례한다. 그리고 그 가운데, 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

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다