D3.js v4/v5 範囲選択 (d3.brush) 使い方
D3の範囲選択の使い方を紹介します。サンプルデモでは表示されているグレーの四角をドラッグするか、グラフ上の何もないところをクリック&ドラッグして範囲選択できます。グレーの四角の隅をドラッグすると範囲の大きさを変更できます。
サンプルデモ
サンプルプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
<!DOCTYPE html> <head> <meta charset="utf-8"> <title>D3 Brush</title> <script src="https://d3js.org/d3.v5.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() { var x0 = xScale.invert(d3.event.selection[0][0]); var y1 = yScale.invert(d3.event.selection[0][1]); var x1 = xScale.invert(d3.event.selection[1][0]); var y0 = yScale.invert(d3.event.selection[1][1]); dot.classed("selected", function(d) { return(x0 <= d[0] && d[0] <= x1) && (y0 <= d[1] && d[1] <= y1); } ); } </script> </body> |
解説
1. 選択時のスタイル設定
選択時にプロットに設定するスタイルを定義しておきます。
1 2 3 4 5 6 |
<style> .selected { fill: red; stroke: brown; } </style> |
2. 散布図の表示
散布図の作り方の詳細は、こちらを参照ください。関連する部分を解説します。
散布図の元データを作成します。
1 2 3 |
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点の二次元配列データを作成します。
1 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"要素を設定します。プロットの表示と範囲選択用のbrush用を"g"要素内に設定します。
3. brushの設定
範囲選択用のbrushを設定するプロトタイプを設定し、callメソッドで呼び出すことでbrushを設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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"要素)に設定されます。
1 2 3 4 5 6 7 8 9 10 11 12 |
<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() | brushの選択範囲を移動させます。[[x0, y0], [x1, y1]]の2次元配列で設定します。 |
d3.brush .extent() | brushの移動可能範囲を設定します。[[x0, y0], [x1, y1]]の2次元配列で設定します。 |
d3.brush .handleSize() | brushのハンドルサイズを設定します。デフォルトは6です。 |
d3.brush .on(typenames, function) | イベント時に呼び出す関数を設定します。typenamesは次の三種類から設定します。 start - 選択開始時。 brush - 範囲選択の変更時。 end - 選択終了時。 |
今回はonメソッドを使って、start, brush時に呼び出すイベントリスナー(brushed)を設定します。
1 |
.on("start brush", brushed); |
イベントリスナー(イベント時に呼び出される関数)は、イベントリスナーが呼び出された時に設定される、d3.eventのフィールドを使って設定します。
d3.event .target | 関連するbrushビヘイビアへの参照 |
d3.event .type | 現在のイベント。“start”、“brush”、“end”のどれか。 |
d3.event .selection | 現在のbrushの選択範囲。[[x0, y0], [x1, y1]]の2次元配列。 |
d3.event .sourceEvent | mousemove、touchmoveなどbrushイベントの元となるイベント種類 |
フィールドのselectionを使って選択された範囲内の散布図のプロットにスタイルを設定します。.invertは散布図上の座標を画面上の座標へ変換する関数xScale,yScaleを逆変換して、画面上の座標から散布図上の座標に変換する関数に変更するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 |
function brushed() { var x0 = xScale.invert(d3.event.selection[0][0]); var y1 = yScale.invert(d3.event.selection[0][1]); var x1 = xScale.invert(d3.event.selection[1][0]); var y0 = yScale.invert(d3.event.selection[1][1]); dot.classed("selected", function(d) { return(x0 <= d[0] && d[0] <= x1) && (y0 <= d[1] && d[1] <= y1); } ); } |
classedメソッドを使って、第二引数の条件式が真のものに"selected"クラスを設定し、偽のものから除外します。
まとめ
タッチパネルだと範囲選択がしずらく、こちらの方法が有効です。。