← Blog
Frontend16 min czytania

D3.js — wizualizacja danych sterowana dokumentem

Sto trzynaście tysięcy gwiazdek, siedemdziesiąt sześć milionów pobrań miesięcznie i dwadzieścia otwartych zgłoszeń. D3 nie jest biblioteką wykresów i nigdy nią nie było — to trzydzieści osobnych modułów, z których większość nie dotyka DOM-u, więc w Reakcie używa się ich deklaratywnie w JSX. Wyjaśniamy, czym jest złączenie danych, kiedy sięgnąć po D3, a kiedy autorzy sami odsyłają do Observable Plot.

„Potrzebujemy wykresu" to zdanie, które w projekcie klienckim pojawia się co najmniej raz. I niemal zawsze pociąga za sobą to samo nieporozumienie: ktoś sięga po D3, bo to najbardziej znana biblioteka do wizualizacji danych — a po dwóch dniach ma sto linii kodu, brzydki wykres słupkowy i pytanie, dlaczego to tyle trwało.

Nieporozumienie polega na jednym: D3 nie jest biblioteką wykresów. Nie ma w niej pojęcia „wykres". Dokumentacja mówi to w pierwszej sekcji, wprost i bez owijania, a mimo to jest to najczęściej mijana informacja w całym świecie frontendu.

D3 (github.com/d3/d3) jest zestawem niskopoziomowych narzędzi do budowania wizualizacji na standardach sieciowych — SVG, Canvasie i DOM-ie. Ten wpis jest o tym, czym te narzędzia są, kiedy warto ich użyć, i kiedy sami autorzy odsyłają gdzie indziej.

Stan projektu, czyli jak wygląda biblioteka skończona

Dane z API GitHuba i npm na 3 października 2026:

  • 113 702 gwiazdki i 22 661 forków; repozytorium założone 27 września 2010 — szesnaście lat,
  • 76,6 miliona pobrań z npm w ostatnim miesiącu,
  • licencja ISC, ostatnie wydanie 7.9.0 z 12 marca 2024,
  • i liczba, która mówi najwięcej: dwadzieścia otwartych zgłoszeń.

Zestawienie „brak wydania od trzydziestu miesięcy" z „siedemdziesiąt sześć milionów pobrań miesięcznie" wygląda niepokojąco, więc trzeba je wyjaśnić, bo wniosek jest inny niż przy poprzednich projektach z tej serii. Repozytorium d3/d3 jest paczką parasolową — nie zawiera prawie żadnego kodu wizualizacji, tylko zależności do trzydziestu osobnych modułów, każdego w swoim repozytorium. Rozwój odbywa się tam.

Sprawdziłem najważniejsze z nich: d3-scale miał push w lutym 2026, d3-array w marcu 2025, d3-selection w styczniu 2025, a d3-shape i d3-force — odpowiednio w październiku i grudniu 2023. Otwartych zgłoszeń mają po kilkanaście do trzydziestu kilku.

To nie jest obraz projektu porzuconego, a projektu skończonego. Skala liniowa nie wymaga nowych funkcji. Generator kształtu obszaru też nie. Dwadzieścia otwartych zgłoszeń przy takiej liczbie pobrań jest podpisem stabilnego API, nie zaniedbania — ale warto wiedzieć, czego się nie doczekamy: nowych możliwości.

D3 powstało w 2011 roku jako praca Mike'a Bostocka, Jeffa Heera i Vadima Ogievetskiego na Stanfordzie. Dziś Bostock i Philippe Rivière utrzymują D3 oraz Observable Plot w firmie Observable — i to drugie narzędzie będzie ważne dla wniosków tego wpisu.

Trzydzieści modułów, nie jedna biblioteka

Dokumentacja podaje przykład, który najlepiej tłumaczy, czym jest praca z D3. Żeby zrobić wykres warstwowy skumulowany, składasz:

  • parser CSV, żeby wczytać dane (d3-dsv),
  • skalę czasu na pozycję poziomą i skalę liniową na pionową (d3-scale),
  • skalę porządkową i schemat kategorialny na kolor (d3-scale-chromatic),
  • układ stosu, żeby ułożyć wartości (d3-shape),
  • kształt obszaru z krzywą liniową, żeby wygenerować dane ścieżki SVG,
  • osie, żeby udokumentować kodowanie pozycji (d3-axis),
  • i selekcje, żeby utworzyć elementy SVG (d3-selection).

Osiem klocków na jeden wykres. Kluczowa własność jest jednak taka, że każdy z nich działa niezależnie — paczka zbiorcza istnieje dla wygody, nie z konieczności. To ma bardzo praktyczną konsekwencję: jeśli potrzebujesz tylko skal i formatowania liczb, instalujesz d3-scale i d3-format, a nie całe D3. Przy dbałości o rozmiar pakietu jest to różnica rzędu wielkości.

Trzydzieści modułów układa się w sześć grup:

  • dane — d3-array (statystyki, grupowanie, przedziały), d3-dsv (CSV i TSV), d3-fetch, d3-random,
  • skale i formaty — d3-scale, d3-scale-chromatic (palety), d3-color, d3-interpolate, d3-format (liczby), d3-time i d3-time-format (interwały i formatowanie dat),
  • kształty — d3-shape (linie, obszary, łuki, stosy, krzywe), d3-path, d3-polygon, d3-contour (izolinie), d3-delaunay (triangulacja i diagramy Woronoja),
  • układy — d3-hierarchy (drzewa, mapy drzewiaste, wykresy pakowane), d3-force (symulacja sił), d3-chord, d3-quadtree,
  • DOM i interakcja — d3-selection, d3-transition, d3-axis, d3-drag, d3-zoom, d3-brush (zaznaczanie zakresu), d3-dispatch, d3-timer, d3-ease,
  • geografia — d3-geo z systemem projekcji kartograficznych.

Warto zauważyć, gdzie leży granica: tylko dziewięć z trzydziestu modułów dotyka DOM-u. Reszta operuje wyłącznie na danych — i to jest fakt, który za chwilę rozwiąże problem łączenia D3 z Reactem.

Złączenie danych, czyli jedyny naprawdę nowy pomysł

Dokumentacja nazywa złączenie danych najbardziej nowatorskim pojęciem D3 i ma rację. Mając zbiór danych i zbiór elementów DOM, złączenie pozwala zastosować osobne operacje dla elementów wchodzących, aktualizowanych i wychodzących.

Po co? Nie po to, żeby narysować wykres — po to, żeby kontrolować dokładnie, co się stanie, gdy dane się zmienią. Efekt jest podwójny: aktualizacje są bardzo wydajne, bo dotykasz wyłącznie tych elementów i atrybutów, które faktycznie wymagają zmiany, bez porównywania drzewa DOM — i dostajesz gładkie przejścia animowane między stanami.

Autorzy dodają przy tym uczciwe zastrzeżenie, którego zwykle w dokumentacjach nie ma: jeśli tworzysz wyłącznie wykresy statyczne, to pojęcie może wydać się nieintuicyjne albo wręcz dziwaczne — bo nie jest do niczego potrzebne. Złączenie danych jest odpowiedzią na problem dynamiki. Kto rysuje jeden nieruchomy wykres, płaci koncepcyjną cenę za funkcję, z której nie korzysta.

Jak to wygląda w kodzie

Wywołanie wiążące dane zwraca selekcję aktualizowaną i definiuje na niej selekcje wchodzącą oraz wychodzącą. Ręczne żonglowanie tymi trzema było przez lata rytuałem inicjacyjnym D3 — dziś jest na to skrót:

svg.selectAll("circle")
  .data(data)
  .join("circle")
    .attr("fill", "none")
    .attr("stroke", "black");

Nazwa elementu podana jako łańcuch znaków jest skrótem; pełna forma pozwala obsłużyć każdą z trzech faz osobno — i to jest miejsce, w którym wpisuje się przejścia animowane dla elementów pojawiających się i znikających:

.join(
  enter => enter.append("circle"),
  update => update,   // domyślnie funkcja tożsamościowa
  exit => exit.remove()
)

Drugi mechanizm, o którym warto wiedzieć, to funkcja klucza. Bez niej dane są przypisywane do elementów po indeksie: pierwsza wartość do pierwszego elementu i tak dalej. Z funkcją klucza wartość trafia do elementu o pasującym kluczu — a duplikaty zachowują się w sposób, który trzeba znać: powtórzone elementy idą do selekcji wychodzącej, a powtórzone dane do wchodzącej.

Znaczenie tego jest praktyczne i widać je dopiero przy animacji. Przy złączeniu po indeksie posortowanie listy sprawia, że słupki „przeskakują" wartościami, bo element numer trzy dostaje po prostu nową liczbę. Przy złączeniu po kluczu ten sam element wędruje na nową pozycję — czyli zachowuje tożsamość. Dane przypisane elementowi są przy tym „lepkie": zostają w jego właściwości __data__ i są dostępne przy ponownej selekcji.

Jest jeszcze jeden szczegół, który potrafi zająć wieczór: selekcje aktualizowana i wchodząca wracają w kolejności danych, a wychodząca zachowuje kolejność z przed złączenia. Przy podanej funkcji klucza kolejność elementów w selekcji może więc nie odpowiadać kolejności w dokumencie — i wtedy trzeba jawnie uporządkować.

Skale: trzynaście rodzin i jedna klasyczna pomyłka

d3-scale jest modułem, po który sięga się najczęściej, i dokumentacja opisuje w nim trzynaście osobno udokumentowanych rodzin skal: liniową, logarytmiczną, potęgową, symetryczno-logarytmiczną, czasową, sekwencyjną, rozbieżną, kwantyzującą, kwantylową, progową, porządkową, pasmową i punktową.

Skala jest funkcją odwzorowującą dziedzinę na zakres, a nowa, dwuargumentowa forma wywołania jest zwięzła:

const x = d3.scaleLinear([0, 100], [marginLeft, width - marginRight]);
const y = d3.scaleTime([start, end], [height - marginBottom, marginTop]);

Zwróć uwagę na odwrócony zakres w skali pionowej — to nie pomyłka, a konieczność: w SVG początek układu współrzędnych jest w lewym górnym rogu, więc większa wartość musi dawać mniejszy y. Pominięcie tego jest przyczyną wykresu narysowanego do góry nogami i pierwszym rytuałem każdego, kto zaczyna z D3.

A klasyczna pomyłka dotyczy dwóch skal dla wymiarów kategorialnych. Skala pasmowa jest jak porządkowa, tylko jej zakres jest ciągły i liczbowy: dzieli go na jednakowe pasma i podaje ich szerokość — i to jej, jak mówi dokumentacja, używa się typowo do wykresów słupkowych. Skala punktowa jest osobną rodziną dla tych samych kategorii, ale bez szerokości pasma. Wybranie porządkowej albo punktowej tam, gdzie potrzebna jest pasmowa, kończy się słupkami o zerowej szerokości i szukaniem błędu w danych, których nie ma.

D3 w Reakcie: dwa tryby, jedna granica

To jest najbardziej praktyczna część dla naszego stosu i odpowiedź, którą warto znać przed pierwszą próbą. Oficjalna dokumentacja dzieli sprawę dokładnie tam, gdzie leży granica modułów.

Tryb pierwszy: moduły niedotykające DOM-u. Skale, tablice, interpolacja, formatowanie — używa się ich w Reakcie bez żadnej różnicy, bo są czystymi funkcjami na danych. Wykres powstaje wtedy w pełni deklaratywnie, w JSX:

const x = d3.scaleLinear([0, data.length - 1], [marginLeft, width - marginRight]);
const y = d3.scaleLinear(d3.extent(data), [height - marginBottom, marginTop]);
const line = d3.line((d, i) => x(i), y);

return (
  <svg width={width} height={height}>
    <path fill="none" stroke="currentColor" strokeWidth="1.5" d={line(data)} />
  </svg>
);

Zwróć uwagę, co się tu dzieje: D3 liczy, React rysuje. Nie ma żadnej selekcji, żadnej mutacji DOM-u, żadnego useEffect. To jest wzorzec, którym powinna zaczynać się każda wizualizacja w aplikacji reactowej — działa też przy renderowaniu po stronie serwera, co przy Inertii ma znaczenie.

Tryb drugi: moduły operujące na selekcjach. d3-selection, d3-transition i d3-axis mutują DOM, więc konkurują z wirtualnym DOM-em Reacta. Tu dokumentacja podaje jedno rozwiązanie: przypiąć referencję do elementu i przekazać ją D3 w haku useEffect:

const gx = useRef();
const x = d3.scaleLinear([0, data.length - 1], [marginLeft, width - marginRight]);

useEffect(() => void d3.select(gx.current).call(d3.axisBottom(x)), [gx, x]);

// w JSX: <g ref={gx} transform={`translate(0,${height - marginBottom})`} />

Praktyczny wniosek z tych dwóch trybów jest jeden i warto go zapamiętać jako regułę: rysuj elementy Reactem, a D3 używaj do obliczeń — z wyjątkiem osi, przejść i interakcji, które przekaż mu przez referencję do wydzielonego elementu g. Mieszanie tych porządków w jednym elemencie jest przyczyną większości dziwnych zachowań, jakie się przy tym łączeniu spotyka.

Kiedy D3, kiedy Plot, kiedy nic z tego

Ta sekcja jest najbardziej wartościowa, a jednocześnie w całości oparta na tym, co piszą sami autorzy — bo trudno o bardziej wiarygodne źródło zastrzeżeń wobec narzędzia niż jego twórcy.

Dokumentacja formułuje to tak: D3 jest alternatywą dla robienia wszystkiego samemu, a nie alternatywą dla wysokopoziomowej biblioteki wykresów. Jeśli inne narzędzia nie wystarczają i myślisz o pisaniu własnych wykresów wprost w SVG, Canvasie albo WebGL — wtedy warto przejrzeć skrzynkę narzędziową D3, bo prawie na pewno coś w niej skróci tę pracę.

Do tego dochodzą trzy zdania, które w dokumentacji stoją czarno na białym:

  • D3 czyni rzeczy możliwymi, nie łatwymi — nawet proste rzeczy, które powinny być łatwe, często nie są,
  • D3 ma sens dla redakcji w rodzaju „New York Timesa", gdzie jedna grafika ma milion czytelników, a nad nią pracuje zespół redaktorów,
  • D3 jest przerostem do prywatnego pulpitu albo jednorazowej analizy — a autorzy dodają ostrzeżenie, żeby nie dać się uwieść efektownym przykładom, bo wiele z nich wymagało ogromnego nakładu pracy.

Alternatywą, do której odsyłają, jest Observable Plot — biblioteka wysokopoziomowa tego samego zespołu, również na licencji ISC. Porównanie z dokumentacji jest bezlitosne dla D3 w typowym zastosowaniu: histogram, który w D3 wymaga pięćdziesięciu linii, w Plocie zajmuje jedną. Sprawdziłem przy tym, jak wygląda utrzymanie: repozytorium Plota miało push 1 września 2026 i ma 347 otwartych zgłoszeń — czyli jest tam, gdzie D3 już nie jest, w fazie aktywnego rozwoju.

Trzeci wariant to biblioteki wykresów dla Reacta w rodzaju Rechartsa czy Chart.js. Praktyczna reguła, którą stosuję:

  • standardowy wykres w panelu klienta — biblioteka wykresów dla Reacta; skończone w godzinę, wygląda dobrze i utrzyma to następna osoba,
  • analiza danych, eksploracja, szybkie zestawienie — Observable Plot,
  • grafika nietypowa, interaktywna, z animowanymi przejściami między stanami, której nie da się wyrazić żadnym gotowym typem wykresu — D3,
  • tylko obliczenia: skale, przedziały histogramu, palety, formatowanie osi czasu — pojedyncze moduły D3 obok dowolnej z powyższych opcji. To jest najczęstsze i najbardziej niedoceniane zastosowanie.

Zalety, o których się zapomina

Dwie rzeczy warto docenić, bo wynikają z fundamentalnej decyzji projektowej — D3 nie wprowadza własnej reprezentacji graficznej, tylko używa standardów sieciowych.

Po pierwsze, wykresy stylizuje się arkuszami stylów, także w reakcji na zapytania medialne — więc responsywność i tryb ciemny są sprawą CSS-a, nie konfiguracji biblioteki. Po drugie, do diagnostyki używa się inspektora elementów i debugera przeglądarki, bo w drzewie DOM leżą zwyczajne elementy SVG. Dokumentacja zwraca przy tym uwagę na rzecz, której zwykle się nie docenia: synchroniczny, imperatywny model wykonania — wywołanie modyfikujące atrybut zmienia DOM natychmiast — bywa łatwiejszy w diagnozowaniu niż frameworki ze złożonym środowiskiem asynchronicznym.

Pułapki

  • D3 to nie biblioteka wykresów. Pierwsza i najkosztowniejsza pomyłka; kosztuje dni, nie godziny,
  • Brak nowych funkcji. Paczka zbiorcza nie miała wydania od marca 2024, a moduły dostają poprawki, nie rozwój. Do biblioteki skończonej to właściwe zachowanie, ale nie licz na to, że pojawi się brakująca funkcja,
  • Nie instaluj całego D3, jeśli potrzebujesz dwóch modułów — paczka zbiorcza to wygoda, nie wymóg,
  • Selekcje i przejścia konkurują z Reactem. Trzymaj je w wydzielonych elementach z referencją i nie renderuj tej samej gałęzi DOM-u z dwóch stron,
  • Złączenie danych jest zbędnym obciążeniem przy wykresach statycznych i autorzy mówią to sami,
  • Efektowne przykłady są mylące. Wiele z nich to praca zespołowa na wiele dni; skopiowanie takiego przykładu bez zrozumienia daje kod, którego nikt potem nie zmodyfikuje,
  • Dostępność jest po Twojej stronie. Skoro D3 nie ma pojęcia wykresu, to nie ma też pojęcia opisu wykresu — atrybuty ARIA, tekst alternatywny i kolejność odczytu trzeba dopisać samodzielnie,
  • Wydajność przy dużych zbiorach: SVG kończy się w okolicach kilku tysięcy elementów. Powyżej trzeba Canvasu, a wtedy część korzyści ze standardów sieciowych — inspektor, CSS, dostępność — przepada,
  • d3-geo ma dodatkową notę licencyjną — o tym w ostatnim akapicie.

Gdzie to ma sens w naszej pracy

  • Skale i formatowanie w istniejących komponentach. d3-scale do przeliczeń, d3-format do liczb, d3-time-format do osi czasu — obok dowolnej biblioteki wykresów. Najtańsze możliwe wejście w D3,
  • Przedziały i statystyki po stronie klienta. d3-array ma grupowanie, przedziały histogramu, kwantyle i sumy narastające. Często wystarcza to zamiast liczenia agregatów w bazie,
  • Wykresy hierarchii. Mapa drzewiasta struktury kosztów albo wykres pakowany kategorii produktów to jedno wywołanie d3-hierarchy i trochę SVG. Żadna gotowa biblioteka nie zrobi tego lepiej,
  • Diagramy relacji. d3-force do grafu powiązań między podmiotami — z przeciąganiem węzłów, bo d3-drag jest obok,
  • Mapy. d3-geo rysuje kartogram Polski z pliku GeoJSON bez żadnej biblioteki mapowej i bez kafelków od zewnętrznego dostawcy. Przy prostych mapach statystycznych jest to rozwiązanie i lżejsze, i tańsze,
  • Grafika na stronę ofertową albo raport roczny klienta — jedno miejsce, w którym nakład pracy na wizualizację nietypową faktycznie się zwraca, bo grafika ma być zapamiętana.

Podsumowanie

  • D3 jest skrzynką narzędziową, nie biblioteką wykresów — nie ma w niej pojęcia „wykres",
  • Trzydzieści niezależnych modułów; instaluj te, których używasz,
  • Tylko dziewięć modułów dotyka DOM-u. Pozostałe to czyste funkcje na danych i tam leży najwięcej wartości dla typowego projektu,
  • W Reakcie: D3 liczy, React rysuje. Osie, przejścia i interakcje przez referencję w useEffect,
  • Złączenie danych jest po to, żeby panować nad zmianą danych — przy wykresach statycznych nie jest potrzebne, a przy animowanych decyduje o nim funkcja klucza,
  • Do słupków bierz skalę pasmową, nie porządkową ani punktową, i pamiętaj o odwróceniu zakresu w osi pionowej,
  • Autorzy sami odsyłają do Observable Plot, gdy nie potrzebujesz niskopoziomowej kontroli: histogram w jednej linii wobec pięćdziesięciu,
  • Plot jest aktywnie rozwijany, D3 jest skończone — obie biblioteki tego samego zespołu, obie na ISC, i można je łączyć,
  • Dwadzieścia otwartych zgłoszeń przy 76 milionach pobrań miesięcznie to podpis dojrzałości, ale też zapowiedź braku nowych funkcji,
  • Dostępność i wydajność są po Twojej stronie, bo biblioteka nie zna pojęcia wykresu.

Licencja: D3 jest na ISC — licencji permisywnej, funkcjonalnie równoważnej MIT i uproszczonej BSD, tylko krótszej. Wolno używać komercyjnie, modyfikować, wpinać w produkty zamknięte i redystrybuować; jedynym obowiązkiem jest zachowanie noty o prawach autorskich i treści licencji we wszystkich kopiach. Nie ma tu open core, wersji korporacyjnej ani żadnej funkcji za paywallem, a Observable Plot od tych samych autorów jest na tej samej licencji — więc pod kątem prawnym cały ten stos wizualizacyjny jest w praktyce bezproblemowy. Jedna rzecz wymaga jednak uwagi przy audycie licencji w projekcie klienckim: modul d3-geo nie ma czystej noty ISC. Obok praw autorskich Mike'a Bostocka zawiera drugą notę licencyjną — biblioteki GeographicLib autorstwa Charlesa Karneya, wykorzystanej w obliczeniach geodezyjnych. Jest to licencja permisywna typu MIT i nie zmienia niczego w tym, co wolno zrobić z kodem, ale oznacza dwa wpisy w zestawieniu licencji zależności, nie jeden — a narzędzia skanujące zależności oznaczają ten modul jako „licencja nierozpoznana", co przy formalnym przeglądzie u klienta z procedurami wymaga wyjaśnienia. Warto wiedzieć zawczasu, żeby nie tłumaczyć tego pod presją terminu.