tech-book-labs
可視化(チャート / 図 / 表) · 最終検証 2026-05-11 · d3 7.x · 初公開 2026-05-11

D3 で React の棒グラフに transition を載せる

D3 を React と組み合わせて、データ更新時に bar の高さ・軸ラベル・数値 text が同時に transition で動く棒グラフを組むパターン。selection / scale / generator の 3 概念、useRef + useEffect の協調、enter/update/exit の data join、tween による数値カウント、を動く demo で確認する実装メモ。

d3 svg react transition data-visualization

検証日: 2026-05-11

使用バージョン: d3@7.x / React 19 + TypeScript 6

対象: Recharts のような高水準ライブラリは触った、もう一段カスタマイズしたい場面で D3 直接書きが必要になった人

Recharts でできない動き(独自 transition、軸の自由装飾、data 数の動的増減)が必要になった時、選択肢は D3 直接書き。本記事は最小の動く例(棒グラフ + 数値 tween)を React + TypeScript で組むパターンの整理。動く demo 付き。

触って試す

データセットを切り替えると、bar の高さ + x 軸 label + 数値 text が同時に滑らかに更新される。これが Recharts の標準 transition では難しいケース(軸 label まで連動して動かす / 数値を「カウントアップ」風に補間する)。

D3 と Recharts の使い分け

「Recharts で十分か、D3 直接書きが必要か」を最初に判断する。

場面選択
標準的な bar / line / pie / areaRecharts(JSX で完結、demo は /articles/recharts-v3-bar-chart-recipes/)
凡例 / Tooltip の細部カスタマイズRecharts + 自前 content(/articles/recharts-v3-advanced-customization/)
数値が少しずつ動く / カウントアップD3 の tween(本記事)
軸 / 罫線の独自装飾D3 の axis + 直接 SVG 操作
ネットワーク図 / force-directedD3 の d3-force(別記事候補)
地図(Mapbox 系)deck.gl / Mapbox GL JS(別系統)

D3 は 低レベル で書く範囲が広い分、Recharts より コード量と学習量 が増える。「Recharts で詰まる具体ケース」だけ D3 に切り替えるのが現実的。

D3 の中心 3 概念

D3 は学ぶ前に 3 つの用語 を押さえると見通しが良くなる:

D3 の 3 つの中心概念 — selection で DOM を取り、scale で値を変換、generator で描画を出す (クリックで拡大)
用語役割
selection(DOM 要素の集合)「jQuery の $(...) の SVG 版」d3.select("svg").selectAll("rect")
scale(値 → ピクセル変換)データ値 100 を SVG の x=150px に変換する関数d3.scaleLinear().domain([0, 1000]).range([0, 400])
generator(SVG 描画指示)折れ線・面・円弧を data から d=... 文字列に変換d3.line() / d3.area() / d3.arc()

これら 3 つを組み合わせて、<svg> の中に好きな図形を生成する。

React + D3 の協調パターン

React の 「DOM は React が管理する」 と D3 の 「DOM は D3 が直接いじる」 は思想が真逆。これを 1 箇所の境界で切り替える:

import { useEffect, useRef } from "react";
import * as d3 from "d3";

export function MyChart({ data }: { data: Datum[] }) {
  const svgRef = useRef<SVGSVGElement | null>(null);

  useEffect(() => {
    const svg = d3.select(svgRef.current);
    if (svg.empty()) return;
    // ここから先は D3 の世界
    // svg.append(...).attr(...).data(data).join(...)
  }, [data]);

  return <svg ref={svgRef} viewBox="0 0 400 200" />;
}

境界の決め方:

  • React 側:<svg> の枠 + ref のみ(React は中身に手を出さない)
  • D3 側:useEffect の中で d3.select(ref) で取って書く(D3 が中身を全責任管理)
  • 依存配列に [data]:data が変わるたび effect が走り、D3 の data join で再描画

この境界を守ると、React の re-render と D3 の DOM 操作が衝突しない。

scale で値域を pixel に変換

棒グラフの場合、x 軸が カテゴリ(label)、y 軸が 連続値(value):

// x: カテゴリ → 等間隔の x 座標(scaleBand)
const x = d3.scaleBand<string>()
  .domain(data.map((d) => d.label))    // ["1Q", "2Q", "3Q", "4Q"]
  .range([0, INNER_W])                  // 0px ↔ 364px
  .padding(0.2);                        // bar 間の隙間

// y: 0 から data の最大値 → 縦方向の pixel(上下反転)
const y = d3.scaleLinear()
  .domain([0, d3.max(data, (d) => d.value) ?? 0])
  .nice()                               // 軸目盛をきりの良い数に
  .range([INNER_H, 0]);                 // 注意: 高さは 0(下) → INNER_H(上)

yrange([INNER_H, 0]) が逆転しているのは、SVG は左上原点なので「画面下に行くほど y 座標が増える」から。値が大きい bar が「上に伸びる」ように見せるには、y 軸の range を反転する。

data join — enter / update / exit

data join(D3 の中心パターン)= 「データ配列の各要素に DOM 要素を 1 対 1 で対応付ける」考え方。data().join()新しい data に対して、DOM の rect 群を 3 通りに振り分ける:

  • enter(新規):data にあるが DOM に無い → 新規 append
  • update(継続):data にも DOM にもある → 属性更新
  • exit(消滅):DOM にあるが data から消えた → remove
bars.selectAll<SVGRectElement, Datum>("rect")
  .data(data, (d) => d.label)         // key で identity を決める
  .join(
    (enter) => enter.append("rect")    // 新 data: rect を新規作成
      .attr("x", (d) => x(d.label))
      .attr("y", INNER_H)              // 初期は床から
      .attr("height", 0)
      .call((s) => s.transition().duration(500)
        .attr("y", (d) => y(d.value))
        .attr("height", (d) => INNER_H - y(d.value))),

    (update) => update                  // 既存 data: 値を transition で更新
      .call((s) => s.transition().duration(500)
        .attr("y", (d) => y(d.value))
        .attr("height", (d) => INNER_H - y(d.value))),

    (exit) => exit                      // 消えた data: アニメで縮めて削除
      .transition().duration(500)
      .attr("height", 0)
      .attr("y", INNER_H)
      .remove(),
  );

ポイント:

  • data(data, key) の key 関数 = identity。同じ key の data は update 扱い、新 key は enter、消えた key は exit
  • transition は enter/update/exit 各々で個別に設定:enter は「下から伸びる」、update は「現在位置から新位置に」、exit は「縮めて消える」、と異なる演出が組める
  • .call(...) で transition を chain:join の各分岐は selection を返すので、.call 経由で transition 適用が綺麗

数値を tween でカウント補間

tween(tween animation の略、補間)= 始点と終点の間を t=0..1 で滑らかに繋ぐ仕組み。D3 の tween(name, fn)属性ではなく任意の DOM 操作を時間補間 できる。

「100 → 250 に増える」ような数値表示を、bar の transition と同期して 滑らかにカウントアップ させる:

bars.selectAll<SVGTextElement, Datum>("text.value")
  .data(data, (d) => d.label)
  .join(
    (enter) => enter.append("text")
      .attr("class", "value")
      .text((d) => d.value),

    (update) => update
      .call((s) => s.transition().duration(500)
        .tween("text", function (d) {
          // 現在の表示値 → 新値 を補間する関数を返す
          const interp = d3.interpolateNumber(Number(this.textContent ?? 0), d.value);
          return (t) => { this.textContent = String(Math.round(interp(t))); };
        })),

    (exit) => exit.remove(),
  );

tween("text", fn)属性ではなく任意の DOM 操作を時間補間する仕組み:

  • fn は data ごとに呼ばれ、(t: 0..1) を受け取る関数を返す
  • d3.interpolateNumber(from, to) で「値の補間関数」を作る
  • transition 中、frame ごとに fn(t) が呼ばれて textContent が動く

これは Recharts では 追加の motion ライブラリが必要 な領域。D3 単独で書ける。

軸(axis)を出す

D3 は 軸 component を関数として提供 する:

const xAxis = svg.append("g")
  .attr("class", "x-axis")
  .attr("transform", `translate(${MARGIN.left}, ${MARGIN.top + INNER_H})`);

xAxis.transition().duration(500)
  .call(d3.axisBottom(x))               // ← x scale を渡すだけで軸が描かれる
  .attr("color", "var(--color-ink-soft)");

d3.axisBottom(scale) / d3.axisLeft(scale)scale を見て tick / label を自動生成.transition().call(axis)軸 label の位置も滑らかに動く(scale が変わったらラベル位置も変わる)。

つまずいたポイント

  • useEffect 内で d3.select(ref) した瞬間に null で empty selection:ref が確実に attach された後で動く必要があるが、useEffect は mount 後に走るので OK。ただし strict mode の double-render で初回 effect が cleanup → 再実行されるので、副作用が冪等(data join なら冪等)である必要
  • 依存配列に [data] を入れたが update しない:data 配列の参照が変わってないのに中身を mutate している。新しい配列を作って setState する(setData([...newData]))
  • transition が走らない:.transition().duration(...) を chain せずに直接 .attr(...) してる。transition を attr の前に挟む
  • enter / update / exit を 1 つの .attr(...) で書こうとして見た目が崩れる:enter は「初期値を 0 から」、update は「現在値から新値へ」、exit は「縮める」と挙動が違う。join の 3 引数で個別に書く
  • TypeScript 型がうるさい:d3.select<SVGRectElement, Datum> のように 2 つの generic が必要。selection の DOM 型 + data 型を毎回明示
  • 複数 chart を同 page で書くと state がぶつかる:D3 のコードが SVG ノードに直接書き込むので、useRef を chart ごとに分ける
  • viewBox を指定せずに width="100%" で曲がる:viewBox="0 0 W H" と CSS の width を分離(viewBox = 内部座標系、CSS width = 表示サイズ)

評価

観点評価コメント
自由度SVG / Canvas を直接書ける、できないことはほぼ無い
学習コストselection / scale / data join / tween など独特の概念
TypeScript 型公式型は揃っているが generic 多用、補完だけでは詰まる
バンドル必要 module だけ import すれば 30-50KB 程度(d3-selection + d3-scale + d3-transition 等)
React との協調境界設計を間違えると DOM の責務が曖昧になる

向く / 向かないケース

  • 向く: Recharts で詰まる具体カスタマイズ(独自 transition / 軸装飾 / 数値 tween / network 図)
  • 向く: 静的 SVG を生成する一回限りの可視化(article inline 図など)
  • 向かない: 標準的な dashboard(Recharts で十分、D3 は overkill)
  • 向かない: 大規模可視化(数万要素以上 → Canvas / WebGL ベースの visx, deck.gl 等)

関連 Topic / 関連書籍

この記事と関係する tech-book.net の Topic と、それぞれの Topic に紐づく書籍:

tech-book.net /books/9784839966645

React Native+Expoではじめるスマホアプリ開発 : JavaScriptによるアプリ構築の実際

松澤 太郎 · マイナビ出版 · 2018年

「React Native」は、Facebookが開発しているスマートフォンアプリ向けの開発環境です。ほとんどのコードをJav…

詳細を tech-book.net で見る
tech-book.net /books/9784873119380

Reactハンズオンラーニング 第2版 : Webアプリケーション開発のベストプラクティス

Alex Banks/Eve Porcello/宮崎 空 · オライリー・ジャパン · 2021年 · ¥3,740

Webフロントエンドの「今」を学びたい人へ! Facebookが開発したJavaScrip…

詳細を tech-book.net で見る
tech-book.net /books/9784873117881

Reactビギナーズガイド : コンポーネントベースのフロントエンド開発入門

Stoyan Stefanov/牧野 聡 · オライリー・ジャパン · 2017年 · ¥2,750

FacebookのエンジニアによるReactの入門書! ReactによるコンポーネントベースのWebフロントエンド開発の…

詳細を tech-book.net で見る
tech-book.net /books/9784297129163

TypeScriptとReact/Next.jsでつくる実践Webアプリケーション開発

手島 拓也/吉田 健人/高林 佳稀 · 技術評論社 · 2022年

新しいフロントエンドの入門書決定版! 本書はReact/Next.jsとTypeScriptを用いてWebアプリケーションを開…

詳細を tech-book.net で見る
tech-book.net /books/9784844379546

【POD】React &amp; Gatsby開発入門

竹本 雄貴
詳細を tech-book.net で見る