D3.js -- x軸、y軸の目盛りを描画する & グリッドを描画する

概要

  • D3.js で軸と罫線を描きたい
  • 軸と目盛り、罫線が描けると色々できるのでちゃんと理解したい

結論

  • 目盛り(g要素)の平行移動と、スケールによる出力位置の調整を使い分けるべき(または、その二つのを適宜組み合わせるべし)

下準備 (HTML)

以下のようなHTML(適当だが)を用意しておく。

<body>
    <!-- ここにグラフを描画する -->
    <svg id="svg"></svg>
</body>

描画領域を定義する

SVG要素いっぱいにグラフを描画するわけではないので各方向のマージンを定義しておく。

/* d3.min.js を読み込み済み */

/* この書き方 */
let margin = {
    left: 30,
    right: 30,
    bottom: 50,
    top: 30,
};

/* SVG要素のサイズ */
let width = window.innerWidth;
let height = window.innerHeight;

/* SVG要素を取得 & サイズ設定 */
let svg = d3.select("#svg")
  .attr("width", width)
  .attr("height", height);

スケールを定義する

続いて、スケールを定義する。
scale(入力値)の返り値と、軸の目盛りの位置は対応していなければならないので、(軸の定義にも)スケールが必要だと理解してしまう(よく考えれば当前か)。

/* x, y 軸のスケールを作成する */
let xScale = d3.scaleLinear()    // v4 から scale.linear() ではなくなった
  .domain([0, 100])    // 入力値の範囲
  .range([margin.left, width - margin.right]);    // 出力位置の範囲
let yScale = d3.scaleLinear()
  .domain([0, 24])    // 一日の 00:00 ~ 24:00、みたいなものをイメージした
  .range([margin.top, height - margin.bottom]);

x軸(と目盛り)を描画してみる

d3.axisBottom(scale) とは「SVG要素の下部分に軸を描画する」という意味ではなく、「下に描画するために目盛りとラベルが下向きについているものを作成する」という意味合い。

/* x 軸を描画する */
svg.append("g")
  .attr("class", "x_axis")
  .attr(
    "transform",
    "translate(" + [
      0,
      // 0    // このようにすると画面上部に軸が表示されるので..
      height - margin.bottom    // このように平行移動させる
    ].join(",") + ")"
  )
  .call(
    d3.axisBottom(xScale)
      .ticks(10)
  );

y軸(と目盛り)を描画する

同じような要領で y 軸も描画する。
平行移動しないと左にズレて描画されるので、マージンの値を利用して位置を修正している。

/* y 軸を描画する */
svg.append("g")
  .attr("class", "y_axis")
  .attr(
    "transform",
    "translate(" + [
      //0,    // このようにすると左にズレた状態で軸(目盛り)が描画されるので..
      margin.left,    // 右に平行移動させる
      0
    ].join(",") + ")"
  )
  .call(
    d3.axisLeft(yScale)
      .ticks(24)
  );

動作結果

以下のようになる。
f:id:zdassen:20180214144557j:plain

(個人的に)ポイントだと思ったところ

軸の描画位置を調整するのは g 要素の"transform" & "translate" で行えばよい。
一方で、以下のように
f:id:zdassen:20180214144943j:plain
x軸(と目盛り)が描画領域からはみ出てしまっている場合に平行移動を用いて修正すべきではない
軸における最大値 = スケールで指定した最大値、なのであるから目盛りの上限はスケールで調整しなければならない。この二つをゴチャ混ぜにすると混乱する(した)。

グリッド(罫線)を描画させてみる

目盛のサイズを指定して、罫線として利用する

まず、以下のようにすると

svg.append("g")
  // (略)
  .call(
    d3.axisBottom(xScale)
      .ticks(10)
      .tickSize(-height)    // 負の方向に height だけ伸びよ
  );

f:id:zdassen:20180214164836j:plain
目盛りが本来の方向とは逆に(マイナス方向に) height だけ伸びてくれる。しかし、軸の範囲は height - margin.bottom - margin.top であったので(スケールでそう定義した)、

.tickSize(-height + margin.bottom + margin.top);

と修正すると、
f:id:zdassen:20180214165134j:plain
綺麗に収まってくれる。y 軸についても同様の考え方で設定できる。以下では、加えて tickFormat() で目盛りの値をフォーマットしている。

/* y軸を作成する */
svg.append("g")
  // (略)
  .call(
    d3.axisLeft(yScale)
      .ticks(24)
      .tickSize(-width + margin.left + margin.right)
      .tickFormat(function (d) {
        return d + ":00";
    })
  );

以下のようになる。
f:id:zdassen:20180214165948j:plain

(ちなみに)CSS

以下のようにした。

/* 軸のスタイル */
g.x_axis path, g.y_axis path {
    stroke: lightgray;
    stroke-width: 1px;
}

/* 罫線スタイル */
g.x_axis line, g.y_axis line{
    stroke: lightgray;
    stroke-opacity: 0.7;
    shape-rendering: crispEdges;
}