D3.jsを基本からまとめてみます。
今回は棒チャートを例にして、基本的な描画方法とデータの紐づけについて説明します。
Observableを使う
手っ取り早くD3.jsを使いたい方はObservableというオンラインツールを使いましょう。右上の追加ボタンからNew Notebookを選択して、Simple D3を新規追加します。コードを書くときは{}
の中に記述しましょう。
書き方の順番
- 目的の整理
- グラフの全体構成を考える
- グラフの描画領域(SVG, Scalable Vector Graphics)を定義
- データの準備
- スケールの設定
- データの紐つけ
- 要素の描画
1. 目的の整理
まず、どんなデータをどのように可視化するのかを明確にします。たとえば、「時間経過に伴う売上の推移を折れ線グラフで表示したい」という具体的な要件を書き出します。
2. グラフの全体構成を考える
次に、グラフの基本構成を考えます。以下のようなポイントを確認します。
- 軸は何か? 横軸 (x軸) と縦軸 (y軸) に何を表示するか
- データの範囲はどれくらいか? 横軸は日付か時間か、縦軸はどの範囲の数値か
- グラフの種類: 折れ線グラフや棒グラフ、円グラフなどどのタイプを使うか
この時点で大まかなイメージを作り、次に具体的なコーディングに進みます。
3. グラフの描画領域(SVG, Scalable Vector Graphics)を定義
次に、グラフの描画領域(SVG)を定義します。マージン、幅、高さを決めます。この段階では、最終的なグラフのサイズ感や、軸が邪魔にならないようにマージンの調整を行います。また、チャートを描画するためのSVGコンテナを作成します。これは、グラフを表示するための「キャンバス」になります。
const width = 500;
const height = 300;
const margin = { top: 20, right: 30, bottom: 40, left: 40 };
// SVGを作成
const svg = d3.create("svg") // d3.createを使用
.attr("width", width)
.attr("height", height);
-
const svg
: ここで変数 svg は、作成されたSVG要素を指しており、以降のコードでこの要素に対して操作を加えるために使います。この変数名は任意の名前であり、例えば const chart や const canvas など、他の名前にしても動作に影響はありません
4. データの準備
今回は簡単な配列をデータとして用意します。
const data = [30, 86, 168, 281, 303, 365];
5. スケールの設定
D3.jsのスケールを使って、データを描画するための座標に変換します。ここでは、棒の高さと幅を設定するためにスケールを定義します。
const x = d3.scaleBand()
.domain(d3.range(data.length)) // データ数に応じたバンド幅
.range([margin.left, width - margin.right])
.padding(0.1); // 棒同士の間隔を設定
const y = d3.scaleLinear()
.domain([0, d3.max(data)]) // データの最大値に基づくスケール
.range([height - margin.bottom, margin.top]); // Y軸を上下反転して設定
-
d3.scaleBand()
: バンドスケールを作成しています。バンドスケールは、カテゴリごとに均等に幅を割り当てて、カテゴリごとに離散的な位置を提供します。棒グラフで、各棒の位置と幅を決めるために使われます。 -
.domain(d3.range(data.length))
: スケールの 入力範囲(domain) を設定します。この場合、d3.range(data.length) は data.lengthの数(データの要素数)に応じた連続した数値配列を生成します(例: [0, 1, 2, ..., data.length - 1])。この配列が domain になり、それぞれの値が1つの棒に対応します。 -
.range([margin.left, width - margin.right])
: 出力範囲(range) を設定します。xスケールの出力範囲は、棒グラフを描画する領域の左端から右端までの位置を決定します。この場合、margin.leftはグラフの左端の位置、width - margin.rightはグラフの右端の位置を示します。これにより、棒グラフが描画される領域が確定します。 -
.padding(0.1)
: 棒と棒の間隔を設定します。0.1は各棒の間に10%の余白を持たせることを意味します。棒が互いに少し隙間を空けて表示されます。 -
d3.scaleLinear()
: リニアスケール(線形スケール)を作成しています。リニアスケールは、入力範囲(domain)と出力範囲(range)が線形に対応するスケールです。すなわち、入力範囲の値が直線的に出力範囲の値に変換されます。 -
.domain([0, d3.max(data)])
: **入力範囲(domain)**を設定します。この場合、[0, d3.max(data)]は、データの最小値を0、最大値をd3.max(data)(データ配列内の最大値)として、縦軸の値の範囲を指定しています。これにより、yスケールは0から最大値までをカバーします。 -
.range([height - margin.bottom, margin.top])
: **出力範囲(range)**を設定します。出力範囲はグラフの描画エリアに対応します。height - margin.bottomはグラフの描画エリアの下端を示し、margin.topは上端を示します。この範囲内にデータをマッピングします。 - 上下反転について: HTMLでは座標の(0, 0)が左上にあるため、y軸のスケールは通常反転させて使います。つまり、0(最小値)はグラフの下側に、最大値は上側に配置されます。このため、rangeの指定が[height - margin.bottom, margin.top]という形で、下から上に変換されています。
6. データの紐つけ
次に、データをSVGのrect要素に紐づけます。d3.selectAll()
と.data()
を使ってデータと要素を結びつけます。
svg.selectAll("rect") // SVG内のすべてのrect要素を選択。棒グラフの棒を描画するために使用される四角形の要素
.data(data) // D3のデータ結合機能を使い、配列 data のデータを選択したrect要素に紐づける
.enter() // データに対して新しい要素を作成
.append("rect") // rect(棒)を追加
.attr("x", (d, i) => x(i)) // 各棒のX座標をスケールに基づいて設定
.attr("y", d => y(d)) // 各棒のY座標を設定(データ値に基づいて高さを調整)
.attr("height", d => y(0) - y(d)) // 棒の高さをデータ値で設定
.attr("width", x.bandwidth()) // 棒の幅をバンド幅で設定
.attr("fill", "steelblue"); // 色を設定
-
svg.selectAll("rect")
: SVG内の全ての rect 要素を選択(最初は空)。 -
.data(data)
: data を選択した rect 要素に紐づける。 -
.enter()
: データに対応する新しい rect 要素を作成。 -
.append("rect")
: 新しい rect 要素をSVGに追加。 -
.attr("x", (d, i) => x(i))
: 各棒のX座標を設定。 -
.attr("y", d => y(d))
: 各棒のY座標を設定。 -
.attr("height", d => y(0) - y(d))
: 各棒の高さを設定。 -
.attr("width", x.bandwidth())
: 各棒の幅を設定。 -
.attr("fill", "steelblue")
: 各棒の色を設定。
7. 要素の描画
次に、X軸とY軸をSVGに追加して、データをわかりやすくします。
// X軸を追加
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`) // X軸をグラフの下に配置
.call(d3.axisBottom(x));
// Y軸を追加
svg.append("g")
.attr("transform", `translate(${margin.left},0)`) // Y軸を左側に配置
.call(d3.axisLeft(y));
全体のコード
// グラフの設定
{
const width = 500;
const height = 300;
const margin = { top: 20, right: 30, bottom: 40, left: 40 };
const data = [30, 86, 168, 281, 303, 365];
const x = d3.scaleBand()
.domain(d3.range(data.length))
.range([margin.left, width - margin.right])
.padding(0.1);
const y = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([height - margin.bottom, margin.top]);
const svg = d3.create("svg") // d3.createを使用
.attr("width", width)
.attr("height", height);
// 棒を追加
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => x(i))
.attr("y", d => y(d))
.attr("height", d => y(0) - y(d))
.attr("width", x.bandwidth())
.attr("fill", "steelblue");
// X軸を追加
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x));
// Y軸を追加
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y));
return svg.node(); // SVG要素を返す
}
おまけ
D3.jsの公式チュートリアル