D3.js 範囲選択(d3.brush)の使い方

D3の範囲選択の使い方を紹介する。

サンプルプログラム

サンプルプログラムに表示されている灰色の四角形をドラッグするか、グラフの何もない場所をクリックしてドラッグすると範囲を選択できる。灰色の四角形の端をドラッグすると、範囲の大きさを変更できる。

コードを確認

サンプルコード

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>D3 Brush</title>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>

<body>
  <!-- 1. 選択時のスタイル設定 -->
  <style>
    .selected {
      fill: red;
      stroke: brown;
    }
  </style>
  <script>
    // 2. 散布図を表示
    var width = 800; // グラフ幅
    var height = 600; // グラフ高さ
    var margin = { "top": 30, "bottom": 30, "right": 30, "left": 30 };

    var randomX = d3.randomUniform(0.5, 10);
    var randomY = d3.randomNormal(0.5, 0.12);
    var data = d3.range(500).map(function () { return [randomX(), randomY()]; });

    var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var xScale = d3.scaleLinear()
      .domain([0, 10])
      .range([0, width - margin.right - margin.left]);

    var yScale = d3.scaleLinear()
      .domain([0, 1])
      .range([height - margin.bottom - margin.top, 0]);

    var dot = g.append("g")
      .attr("fill-opacity", 0.2)
      .selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("cx", function (d) { return xScale(d[0]) })
      .attr("cy", function (d) { return yScale(d[1]) })
      .attr("r", 5);

    svg.append("g")
      .attr("transform", "translate(" + margin.left + "," + (height - margin.bottom) + ")")
      .call(d3.axisBottom(xScale));

    svg.append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .call(d3.axisLeft(yScale));

    // 3. brush設定
    var brush = d3.brush()
      .extent([
        [0, 0],
        [width - margin.left - margin.right, height - margin.top - margin.bottom]
      ])
      .on("start brush", brushed);

    g.append("g")
      .call(brush)
      .call(brush.move, [
        [xScale(2), yScale(0.8)],
        [xScale(5), yScale(0.3)]
      ]);

    function brushed(event) {
      var x0 = xScale.invert(event.selection[0][0]);
      var y1 = yScale.invert(event.selection[0][1]);
      var x1 = xScale.invert(event.selection[1][0]);
      var y0 = yScale.invert(event.selection[1][1]);
      dot.classed("selected",
        function (d) {
          return (x0 <= d[0] && d[0] <= x1) && (y0 <= d[1] && d[1] <= y1);
        }
      );
    }
  </script>
</body>

</html>

コード説明

1. 選択時のスタイル設定

選択されたとき、プロットに設定するスタイルを定義する。

<style>
.selected {
  fill: red;
  stroke: brown;
}
</style>

2. 散布図を表示

散布図を作成する方法の詳細はこちらを参照する。ここでは関連部分を説明する。

散布図の元データを作成する。

var randomX = d3.randomUniform(0.5, 10);
var randomY = d3.randomNormal(0.5, 0.12);
var data = d3.range(500).map(function() { return [randomX(), randomY()]; });

d3.randomUniformは引数に設定した範囲で乱数を発生させる関数を設定するもので、d3.randomNormalは平均値と標準偏差を引数としてガウス分布に従う乱数を発生させる関数を設定するものである。これらの関数とd3.range(500).map()を使用して、500点の2次元配列データを作成する。

var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

body要素内にSVG要素を設定し、その中に"g"要素を設定する。プロット表示および範囲選択用のブラシを"g"要素内に設定する。

3. brush設定

範囲選択用のbrushを設定するプロトタイプを設定し、callメソッドで呼び出してbrushを設定する。

var brush = d3.brush()
  .extent([
    [0, 0],
    [width - margin.left - margin.right, height - margin.top - margin.bottom]
  ])
  .on("start brush", brushed);

g.append("g")
  .call(brush)
  .call(brush.move, [
    [xScale(2), yScale(0.8)],
    [xScale(5), yScale(0.3)]
  ]);

callメソッドでbrushを設定すると、次のSVG要素が選択した要素内、今回は"g"要素内に設定される。

<g class="brush" fill="none" pointer-events="all" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">
  <rect class="overlay" pointer-events="all" cursor="crosshair" x="0" y="0" width="960" height="500"></rect>
  <rect class="selection" cursor="move" fill="#777" fill-opacity="0.3" stroke="#fff" shape-rendering="crispEdges" x="112" y="194" width="182" height="83"></rect>
  <rect class="handle handle--n" cursor="ns-resize" x="107" y="189" width="192" height="10"></rect>
  <rect class="handle handle--e" cursor="ew-resize" x="289" y="189" width="10" height="93"></rect>
  <rect class="handle handle--s" cursor="ns-resize" x="107" y="272" width="192" height="10"></rect>
  <rect class="handle handle--w" cursor="ew-resize" x="107" y="189" width="10" height="93"></rect>
  <rect class="handle handle--nw" cursor="nwse-resize" x="107" y="189" width="10" height="10"></rect>
  <rect class="handle handle--ne" cursor="nesw-resize" x="289" y="189" width="10" height="10"></rect>
  <rect class="handle handle--se" cursor="nwse-resize" x="289" y="272" width="10" height="10"></rect>
  <rect class="handle handle--sw" cursor="nesw-resize" x="107" y="272" width="10" height="10"></rect>
</g>

また、d3.brushには次の設定が可能である。

設定 説明
d3.brush.move() ブラシの選択領域を移動する。
[[x0, y0], [x1, y1]]の2次元配列で設定する。
d3.brush.extent() ブラシの移動可能範囲を設定する。
[[x0, y0], [x1, y1]]の2次元配列で設定する。
d3.brush.handleSize() ブラシのハンドルサイズを設定する。
既定値は6である。
d3.brush.on(typenames , function) イベント時に呼び出す関数を設定する。typenamesは次の3種類から設定する。
start - 選択開始時。
brush - 範囲選択の変更時。
end - 選択終了時。

今回はonメソッドを使用して、startbrush時に呼び出すイベントリスナー(brushed)を設定する。

.on("start brush", brushed);

イベントリスナー、つまりイベント時に呼び出される関数は、イベントリスナーが呼び出されたときに設定されるeventのフィールドを使用して設定する。

イベントフィールド 説明
event.target 関連するbrush behaviorへの参照
event.type 現在のイベント。"start""brush""end"のいずれか
event.selection 現在のbrushの選択範囲。[[x0, y0], [x1, y1]]の2次元配列。
event.sourceEvent mousemovetouchmoveなど、brushイベントの元になるイベント種類

フィールド選択を使用して、選択した範囲内の散布図プロットにスタイルを設定する。.invertは、散布図上の座標を画面上の座標へ変換する関数xScaleyScaleを逆変換し、画面上の座標から散布図上の座標へ変換する関数に変更するメソッドである。

function brushed(event) {
  var x0 = xScale.invert(event.selection[0][0]);
  var y1 = yScale.invert(event.selection[0][1]);
  var x1 = xScale.invert(event.selection[1][0]);
  var y0 = yScale.invert(event.selection[1][1]);
  dot.classed("selected",
    function (d) {
      return (x0 <= d[0] && d[0] <= x1) && (y0 <= d[1] && d[1] <= y1);
    }
  );
}

classedメソッドを使用して、第2引数の条件式が真の場合は"selected"クラスを設定し、偽の場合は除外する。