D3.jsでグラフを描画するには、いくつかの下準備がいる。スケールはその第一歩である。これは主に、各データを大きさや色、位置などに置き換える計算を行う。

例えば[0.5, 1.0, 1.5]というデータを棒グラフにすることを考える。各数値をそのままピクセルに置き換えて描画すると、虫眼鏡で見ないとわからないサイズになってしまう。

見やすくするには、各データの比率を保ったままスケールアップする必要がある。

対象は大きさに限らないが、スケールはこうしたグラフ化に不可欠な変換を行う関数であり、オブジェクトである。関数として値を受け取り、変換した値を返すものの、それぞれにユーティリティメソッドも持つ。

スケールを作るには

D3.jsには、スケール関数を作成するいくつかのファクトリメソッドが用意されている。これらのメソッドは、以下のような形で呼び出す。

js
const scaleXXX = d3.scaleXXX(domain, range);
// rangeの代わりに、補間関数を渡すメソッドもある

scaleXXX()メソッドは、入力値の範囲と出力値の範囲を渡すと、内部でその間を埋める関数を作り、その関数を使ってスケール関数を作る。

この入力値の範囲をドメイン、出力値の範囲をレンジ、間を埋めてくれる関数を補間関数(インタポレータ)と呼ぶ。

ドメイン

ドメインは、入力値の範囲を示す。ただ一口にドメインと言っても、スケールによってその内容が異なる。注意が必要である。

範囲を示すもの

範囲を示す、2つ以上の要素を持つ配列。多くは[最小値, 最大値]のような形を取る。2つ以上の値が指定されると、[最大値, 中間値1, ..., 最大値]のように区分されて解釈される。

値は数値あるいはDateオブジェクト色名など数値に強制的に変換できるデータで、かつ昇順 or 降順でなければならない。

このタイプのドメインを使うメソッド
scaleLinear(), scaleTime(), scalePow(), scaleLog(),scaleSyslog(), scaleSequential(), scaleDiverging()

値自体を示すもの

ドメインとレンジをそのまま対応させるタイプのスケールもある。この場合のドメインは、レンジと同数の値を持つ配列である。

このタイプのドメインを使うメソッド
scaleOrdinal(), scaleBand(), scalePoint()

分布とか

※このあたりは難しくてよくわからなかったのでフィーリングでまとめた。ニュアンスが間違っている可能性がある。

D3.jsには、渡された値の範囲に応じて、任意の値を返すスケールが2つ用意されている。クォンタイルスケールとクォンタイズスケールである。

クォンタイルスケールを作成するscaleQuantile()メソッドは、任意の範囲(レンジに指定される範囲)に分割される一連の値をドメインに受け取る。配列は、順序不同の1つ以上の数値あるいは数値に強制変換できる値の配列でなければならない。

また、クォンタイズスケールを作成するscaleQuantize()メソッドは、任意の範囲に分割される値の範囲を受け取る。範囲は、[最小値, 最大値]という2つの要素を持つ配列である。2つの要素が昇順でない場合、スケールの動作は定義されない。

レンジ

数値に限定されるドメインとは異なり、レンジは数値以外も要素にできる。ただし、レンジに渡すデータは、後述する補間関数が理解できるものでなければならない。

レンジの配列の形式も、作成するスケールによっていくつかの種類に分けられる。おおよそは、第一引数に渡されるドメインと対応している。

範囲を示すもの

任意の範囲を別の範囲にマッピングするスケールを作る場合、そのファクトリメソッドの第二引数には、当然範囲が渡される。範囲を表現する配列の要素数は、ドメインと同じである必要がある。もし異なる場合は、一致するまでの範囲がマッピングの対象となる。

このタイプのレンジを使うメソッド
scaleLinear(), scaleTime(), scalePow(), scaleLog(),scaleSyslog(), scaleSequential(), scaleDiverging()

値自体を示すもの

ドメインとレンジを一対一で対応させる時には、ドメインに指定した配列と同数の値をもつ配列をレンジに渡す。

このタイプのレンジを使うメソッド
scaleOrdinal()

1つの範囲を示すもの

任意のデータ(ドメイン)数に対応する、任意の範囲区分を返すスケールがある。こうしたスケールのファクトリメソッドには、[最小値, 最大値]という形のレンジを渡す。

このタイプのレンジを使うメソッド
scaleBand(), scalePoint()

内部で補間関数に置換されるもの

scaleSequential()メソッドとscalescaleDiverging()メソッドは、第二引数に補間関数を受け取ってドメインを任意の値に変換する。

この補間関数の代わりに、レンジを渡せる。scaleSequential()は2つの要素からなる配列、scalescaleDiverging()メソッドは3つの要素からなる配列をレンジと指定できる。

これらの配列は、値の種類に応じて内部で適切な補間関数に変換される。

分布の分割箇所・分類を示すもの

※このあたりは(略。

クォンタイルスケールやクォンタイズスケールを作るファクトリメソッドのレンジには、空ではない配列を指定できる。値の型はなんでもいい。この配列の数によって、ドメインに渡された配列の分類数が決められる。

scaleThreshhod()のように、分割のしきい値を自分で決められるスケールを作るファクトリもある。この場合、ドメインに渡した配列の要素数+1の数の配列をレンジに渡す。

補間関数

D3.jsで言う補間関数は、0から1の範囲の引数を受け取り、任意の値を返す関数である。

スケールファクトリの内部では、渡されたレンジに基づいて適切な補間関数ファクトリが選ばれ、補間関数を作成している。補間関数ファクトリは、2つの値を受け取り、その間を埋める値を返す補間関数を作る。

大抵のスケールファクトリは、引数としてドメインとレンジを受け取る。ただ、レンジの代わりに補間関数を受け取るファクトリもある。scaleSequential()メソッドとscaleDiverging()メソッドである。

また、補間関数ファクトリ補間関数は、色分けやアニメーションなどで頻繁に使う。らしい。

基本的な概要は押さえておきたい。

種類別・スケールファクトリの一覧

D3.jsには、スケールの種類に応じて以下のようなファクトリメソッドが用意されている。

線形のスケール

ファクトリメソッド 概要
scaleLinear(d, r) d:rの比率で、渡された数値を直線上の距離(数値)に変換する関数を返す。
drのデフォルト値は共に[0, 1]
scaleIdentity(r) d:rの比率が同じ場合に使う、ショートハンド的なメソッド(たぶん)。
rのデフォルト値は[0, 1]
scaleRadial(d, r) d:rの比率で、渡された数値を半径上の距離(数値)に変換する関数を返す。
drのデフォルト値は共に[0, 1]

いっぱいあるので、適当に分けて間にサンプルコードを挟みたいと思う。

html
<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head><body>
<!-- ここからサンプルコード -->
<script>
  const linear = d3.scaleLinear([0,1], [0,100]);
  d3.select('body').append('ul').append('li').text(`Linear:${linear(0.25)}`);

  const identity = d3.scaleLinear([0,1], [0,100]);
  d3.select('ul').append('li').text(`Identity:${identity(0.25)}`);

  const radial = d3.scaleRadial([0,1], [0,100]);
   d3.select('ul').append('li').text(`Radial:${radial(0.25)}`);
</script></body></html>

時間と指数、対数のスケール

スケールの種 概要
scaleUtc(d, r) d:rの比率で、渡されたDateオブジェクトをUTCに対応する数値に変換する関数を返す。
dのデフォルト値は[2000-01-01, 2000-01-02]rのデフォルト値は[0, 1]
scaleTime(d, r) d:rの比率で、渡されたDateオブジェクトを現地時間に対応する数値に変換する関数を返す。
dのデフォルト値は[2000-01-01, 2000-01-02]rのデフォルト値は[0, 1]
scalePow(d, r).exponent(n) 渡された値をd:rの比率に直し、n乗した数値に変換する関数を返す。
drのデフォルト値は共に[0, 1]exponent(n)を省略すると、指数は2となる。
scaleSqrt(d, r) 渡された値をd:rの比率に直し、その平方根(Square root)に変換する関数を返す。d3.scalePow(…).exponent(0.5)のショートハンド。
drのデフォルト値は共に[0, 1]
scaleLog(d, r).base(n) 渡された値をd:rの比率に直し、nを基数とする対数に変換する関数を返す。
dのデフォルト値は[1, 10]rのデフォルト値は[0, 1]base(n)を省略すると、基数は2となる。

日付もややこしいし、指数も対数もややこしい。勉強して、がんばって慣れるしかない。

html
<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head><body>
<!-- ここからサンプルコード -->
<script>
  const utc = d3.scaleUtc([new Date('2024-01-01'), new Date('2024-01-02')], [0, 100]);
  d3.select('body').append('ul').append('li').text(`Utc:${utc(new Date('2024-01-01T12:00Z'))}`);

  const time = d3.scaleTime([new Date('2024-01-01'), new Date('2024-01-02')], [0, 100]);
  // UTC+9が日本時間のため、この場合日本では21時が50(半日経過)になる。
  d3.select('ul').append('li').text(`Time:${time(new Date('2024-01-01T21:00'))}`);

  const pow = d3.scalePow([0,1], [0,100]).exponent(3);
  d3.select('ul').append('li').text(`Pow:${pow(0.5)}`);

  const sqrt = d3.scaleSqrt([0,1], [0,100]);
  d3.select('ul').append('li').text(`Sqrt:${sqrt(0.25)}`);

  const log = d3.scaleLog([1, 16], [0, 4]);
  d3.select('ul').append('li').text(`Log:${log(4)}`);
</script></body></html>

ラベルや軸等に使うスケール

ファクトリメソッド 概要
scaleOrdinal(d, r) 順序関係を表す不連続なデータを扱うためのスケール。dの値をそのままrに対応づける関数を返す(連想配列みたいな関数)。
デフォルト値は、共に空の配列。
scaleBand(d, r) 順序関係を表す不連続なデータを、rで渡された範囲の数値に変換する関数を返す。この関数は、棒グラフの描画に役立つ様々なメソッドを持っている。
dのデフォルト値は空で、rのデフォルト値は[0, 1]
scalePoint(d, r) 順序関係を表す不連続なデータを、rで渡された範囲の数値に変換する関数を返す。この関数は、点の描画に役立つ様々なメソッドを持っている。
dのデフォルト値は空で、rのデフォルト値は[0, 1]

scaleOrdinal()は、データに応じてラベル名を表示したり、色などを変えたい場合に役立つ。

scaleBand()は、棒グラフにラベル名をつけたい場合などに使う。関数に含まれるメソッドで、幅や余白の値などの取得・設定もできる。

pointBand()は、scaleBand()の点版のイメージ。散布図や折れ線グラフの頂点を示したい場合などに役立つ。こちらも、関数に含まれるメソッドで点の幅や余白の値などの取得・設定が可能。

html
<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head><body>
<!-- ここからサンプルコード -->
<script>
const ordinal =  d3.scaleOrdinal([1,2,3], ['a','b','c']);
d3.select('body').append('ul').append('li').text(`Ordinal:${ordinal(2)}`);

const band =  d3.scaleBand(['a','b','c','d'],[0,100], );
d3.select('ul').append('li').text(`Band:${band('a')}`).style('margin-top','8px');;
// バンド間の余白を取得 or 設定
d3.select('ul').append('li').text(`Bandの余白:${band.padding()}`);
// バンドを中央寄せするための値を取得 or 設定
d3.select('ul').append('li').text(`Bandの中央寄せのための余白(?):${band.align()}`);
// バンドの幅を取得 or 設定
d3.select('ul').append('li').text(`Bandの幅:${band.bandwidth()}`);

const point =  d3.scalePoint(['a','b','c','d'],[0,100], );
d3.select('ul').append('li').text(`Point:${point('a')}`).style('margin-top','8px');
// 点間の余白を取得 or 設定
d3.select('ul').append('li').text(`Pointの余白:${point.padding()}`);
// 点を中央寄せするための値を取得 or 設定
d3.select('ul').append('li').text(`Pointの中央寄せのための余白(?):${point.align()}`);
// バンドの幅を取得 or 設定
d3.select('ul').append('li').text(`Point:${point.bandwidth()}`);
</script></body></html>

値のグループ化に使うスケール

ファクトリメソッド 概要
scaleQuantile(d, r) dに渡された配列を、rに渡された配列の数に応じて分類する関数を返す。
dr共にデフォルト値は空の配列。
scaleQuantize(d, r) dに渡された範囲の数値を、rに渡された配列の数に応じて分類する関数を返す。
dは範囲を示す2つの値を持つ配列で、デフォルト値は空の配列。rは任意の配列で、デフォルト値は空の配列。
scaleThreshold(d, r) dに渡された配列をしきい値として、rに渡された配列に分類する関数を返す。
dのデフォルト値は[0.5]rのデフォルト値は[0 ,1]

scaleQuantile()は、値と値そのものの数を考慮して分割するポイントが決められる。scaleQuantilze()は、ドメインの範囲をレンジの数で割って分割するポイントが決められる。イメージとしては、前者が中央値、後者が平均値みたいなもの(たぶん)。

scaleThreshold()は分割ポイントを任意で設定できる。

使いどころとしては、ヒートマップというか、データを色分けしたいケースが多いらしい。

html
<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head><body>
<!-- ここからサンプルコード -->
<script>

const quantile =  d3.scaleQuantile([1, 1, 2, 3, 4, 5, 6], ['first','second','third']);
d3.select('body').append('ul').append('li').text(`Quantile 分位値:${quantile.quantiles()}`);
d3.select('ul').append('li').text(`Quantile 1:${quantile(1)}`);
d3.select('ul').append('li').text(`Quantile 2:${quantile(2)}`);
d3.select('ul').append('li').text(`Quantile 4:${quantile(4)}`);

const quantize =  d3.scaleQuantize([1, 6], ['first','second','third']);
d3.select('ul').append('li').text(`Quantize しきい値:${quantize.thresholds()}`).style('margin-top','8px');
d3.select('ul').append('li').text(`Quantize 1:${quantize(1)}`)
d3.select('ul').append('li').text(`Quantize 2:${quantize(2)}`);
d3.select('ul').append('li').text(`Quantize 4:${quantize(4)}`);


const threshold = d3.scaleThreshold([0.5],['Zero', 'One']);
d3.select('ul').append('li').text(`Threshold 0:${threshold(0)}`).style('margin-top','8px');
d3.select('ul').append('li').text(`Threshold 1:${threshold(1)}`);
</script></body></html>

連続する値を扱うスケール

ファクトリメソッド 概要
scaleSequential(d, i) dで受け取った範囲(長さ2の配列)を、iで受け取った補間関数が返す値にマッピングする関数を返す。
引数を1つだけ渡すと、それが関数の場合は補間関数、それ以外はレンジとして解釈される(ソース)。
dのデフォルト値は[0, 1]iのデフォルト値は、渡された値をそのまま返す関数である。
scaleDiverging(d, i) dで受け取った2つの範囲(長さ3の配列)を、iで受け取った2つの範囲を扱う補間関数が返す値にマッピングする関数を返す。
引数を1つだけ渡すと、それが関数の場合は補間関数、それ以外はレンジとして解釈される。
dのデフォルト値は[0, 0.5, 1]iのデフォルト値は、渡された値をそのまま返す関数である。

D3で言う補間関数は、0から1の間にある値を受け取り、任意の2つ値の間にある値に置換して返す関数である。

D3には、数値や色、オブジェクト、配列の各要素などを対象とした補間関数や、インターポレータを作るメソッドがデフォルトで用意されている。

scaleSequential()scaleDiverging()は、線形に値をマッピングする。両者と同じ使い方心地で、対数やべき乗、平方根に対応したスケールを生成するメソッドも用意されている。

html
<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head><body>
<!-- ここからサンプルコード -->
<script>
// d3.interpolateBluesは、0〜1の数値に対応する青色を返す補間関数
const sequential = d3.scaleSequential([0,1], d3.interpolateBlues);
d3.select('body').append('ul').append('li').text(`Sequential 0 : ${sequential(0)}`);
d3.select('ul').append('li').text(`Sequential 0.5 : ${sequential(0.5)}`);
d3.select('ul').append('li').text(`Sequential 1 : ${sequential(1)}`);

// d3.interpolateRdBuは、-1〜0の数値に対応する赤色、0〜1の数値に対応する青色を返す補間関数
const diverging = d3.scaleDiverging([-1,0,1], d3.interpolateRdBu);
d3.select('ul').append('li').text(`Diverging -1 : ${diverging(0)}`).style('margin-top','8px');
d3.select('ul').append('li').text(`Diverging 0 : ${diverging(0.5)}`);
d3.select('ul').append('li').text(`Diverging 1 : ${diverging(1)}`);
</script></body></html>

参考資料