D3.jsでは、専用のメソッドを通してグラフに応じた軸を柔軟に表示できる。はずだ。難しいのでまだ全然わからないが、使いこなせるように勉強した内容をまとめる。

軸を表示するまでの流れ

軸の表示は以下のような流れで行う。

  1. グラフで表現するデータに応じたスケールを作成する。
  2. 軸ジェネレータファクトリにスケール渡し、軸ジェネレータを作成する。
  3. 軸ジェネレータのメソッドを通して、生成する軸の設定を行う。
  4. 任意のSelectioncall()メソッドに軸ジェネレータを渡す。
  5. attr()メソッドで軸の位置を調整する。

ここで言うSelectioncall()メソッドも、D3が独自に用意するもので、JavaScriptのAPIとは全くの別物である。

以下に、軸を表示するための最小限のサンプルコードを示す。

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>
<svg style="display: block; margin: auto;" width="640" height="480"></svg>
<!-- ここからサンプルコード -->
<script>
const domain = [0, 10]//入力する値の範囲
const range = [0, 640 - 128];//出力する値の範囲。余白が欲しいので128を引く。
const linear = d3.scaleLinear(domain, range);

d3.select("svg")
    .append("g")
    .call(d3.axisBottom(linear))
    .attr("transform", `translate(64, 64)`);
</script></body></html>

スケール

一連のデータをグラフ化するには、データを任意のピクセルサイズに置き換える仕組みが必要だ。スケールは、そのための関数であり、オブジェクトである。

軸を表示するには、想定されるデータ範囲に対応したスケールをあらかじめ用意しておく必要がある。

D3.jsには大きく13種類のスケールが用意されているが、軸の表示に使えるのは以下の10種類(とその派生)である。

スケールの種類 概要
scaleLinear 入力値を線形に変換する。一般的な数値データに適用。
scaleTime ローカル時間を連続的な値に変換する。時間軸の作成に適用。
scaleUtc UTC時間を連続的な値に変換する。国際標準時に基づく時間軸の作成に適用。
scalePow 入力値を指数関数的に変換する。非線形スケーリングに適用。
scaleLog 入力値を対数的に変換する。広範な数値範囲に適用。
scaleSqrt 入力値を平方根に基づいて変換する。非線形スケーリングの一種。
scaleSymlog 入力値を対数的に変換する。負の数値を含む広範囲なデータに適用。
scaleOrdinal 離散的な入力値を離散的な出力値に変換する。カテゴリ名等の表示に適用。
scaleBand 離散的な入力値を連続的な出力の範囲に均等に変換する。棒グラフなどの表示に適用。
scalePoint 離散的な入力値を離散的な出力値に均等に変換する。カテゴリ軸の作成に適用。

これ以外のスケールでも、軸自体を表示することはできる。ただ、出力値が数値でないと、実用的な軸の表示は難しい。

軸ジェネレータファクトリ

スケールを用意したら、次は軸ジェネレータファクトリにスケールを渡して軸ジェネレータを生成する。

軸ジェネレータファクトリは、軸の表示形式に応じて以下の4種類が用意されている。

軸ジェネレータファクトリの種類 概要
axisTop(scale) 上側に目盛りを表示する軸を生成する。
axisRight(scale) 右側に目盛りを表示する軸を生成する。
axisBottom(scale) 下側に目盛りを表示する軸を生成する。
axisLeft(scale) 左側に目盛りを表示する軸を生成する。

軸ジェネレータ

軸ジェネレータファクトリにより軸ジェネレータを生成したら、そのメソッドを使って表示する軸の設定を行う。

メソッド名 概要
tickValues(values) 軸に表示される目盛りの値を手動で設定する。値は数値の配列。値に単位を設定するには、tickFormat()を使う。
tickFormat(format) 目盛りのフォーマットを設定または取得する。フォーマットの詳細はここ
tickSize(size) 外側(両端)と内側(両端の間)の目盛りのサイズを一括で設定または取得する。デフォルトは6
tickSizeInner(size) 内側の目盛りのサイズを設定または取得する。デフォルトは6
tickSizeOuter(size) 外側の目盛りのサイズを設定または取得する。デフォルトは6
tickPadding(padding) 目盛りと、目盛りに対応するラベルの間の距離。デフォルトは3
offset(offset) 軸のオフセットを設定または取得する。オフセットが何かは謎。軸と目盛りの差かと思いきや、値を増やすとそれぞれ違う軸方向に移動する。詳細調査中。デフォルトは0
scale(scale) 軸に使用するスケールを設定または取得する。
ticks(…arguments) 各スケールのticks()メソッドとtickFormat()メソッドに渡す値を一括で設定する。具体的な値はスケールによりにけり。
tickArguments(arguments) 何これ。わかりません。D3難しい。ticks()とほぼ同じ?

軸の位置とサイズ

軸はデフォルトでは原点(左肩)に表示される。そのため、グラフに合わせてちょうど良い位置に移動する必要がある。

移動には、transform属性を使うのが一般的である。

ただ、軸のサイズは設定によって異なる。座標を設定するときは、軸の諸々の設定値を計算に含めておかねばならない。

ちなみに、軸のサイズを左右する各部のデフォルトの設定値は以下の通りである。

項目名 デフォルト値 設定用メソッド
ラベルのフォント 10px なし。スタイリングで設定。
ラベルと目盛りの間 3px tickPadding(padding)
目盛り 6px 一括:tickSize(size)
外側:tickSizeOuter(size)
内側:tickSizeInner(size)
目盛りと軸の間 0 offset(offset)
軸の太さ 1px なし。スタイリングで設定。

忘れてはいけないのが軸の太さである。

軸は、デフォルトの配置だと原点から垂直、あるいは水平に伸びる。パスの線は指定した座標が中央に来るように描かれるため、線の半分が描画エリアからはみ出てしまう。1pxの軸は、半分隠れて0.5px分しか見えない。

軸の位置を指定するときは、この特性も念頭に置いておきたい。

軸の表示

軸は、軸ジェネレータオブジェクトをSelectionオブジェクトのcallメソッドに渡すことでレンダリングされる。通常は、<svg>タグの子要素として<g>タグを追加し、このSelectioncallメソッドを使う。

以下、各種スケールを使って軸を表示するサンプルである。

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>
<svg style="display: block; margin: auto;" width="640" height="480"></svg>
<!-- ここからサンプルコード -->
<script>
const range = [0, 640 - 128];
const linear = d3.scaleLinear([0, 10], range);
const time = d3.scaleTime(
  [new Date("2024-01-01"), new Date("2024-01-02")],
  range
);
const utc = d3.scaleUtc(
  [new Date("2024-01-01"), new Date("2024-01-02")],
  range
);
const pow = d3.scalePow([0, 10], range).exponent(2);
const log = d3.scaleLog([1, 8], range);
const sqrt = d3.scaleSqrt([0, 1], range);
const symLog = d3.scaleSymlog([-8, 8], range);
const ordinal = d3.scaleOrdinal(["A", "B", "C"], [0, 100, 512]);
const band = d3.scaleBand(["グループA", "グループB", "グループC"], range);
const point = d3.scalePoint(["い", "ろ", "は", "に"], range);

const scales = [
  ["linear", linear],
  ["time", time],
  ["utc", utc],
  ["pow", pow],
  ["log", log],
  ["sqrt", sqrt],
  ["symLog", symLog],
  ["ordinal", ordinal],
  ["band", band],
  ["point", point],
];

scales.forEach((s, i) => {
  const y = ((480 - 64) / scales.length) * i + 32;
  d3.select("svg")
    .append("text")
    .text(s[0])
    .attr("x", 24)
    .attr("y", y + 8);
  d3.select("svg")
    .append("g")
    .call(d3.axisBottom(s[1]))
    .attr("transform", `translate(100 ${y})`);
});
</script></body></html>

参考資料