NVD3でドラッグされた範囲のデータを再描画する散布図を作ってみた	


[前回の記事で紹介した](http://qiita.com/ganezasan/items/a8b2e48b6da7f7eafc7d)Qiitaのタグの情報を使い、ドラッグして範囲を指定し、ドリルダウンできる散布図を作ってみました。

今回の完成がこちらです。
http://bl.ocks.org/ganezasan/9098135dae6ce1552194

![ドラッグ＆ドリルダウン](https://gist.github.com/ganezasan/9098135dae6ce1552194/raw/f86821a26b9cd28659c10463cebc22caae06eb85/qiitaScatterPlot2.gif)


最初にこのコードは汎用化はできていません。
まず動くというのを目指しました。
そのため、NVD3のライブラリを直接いじっています。
なので、試す際にはこのリポジトリにおいてあるnv.d3.jsを使用しないと動作しません。
今後もう少し綺麗に書いてNVD3にPull Requestを送りたいと思います。


##修正したソースコード部分
``` css
.extent {
  stroke: #cfd7e5;
  fill: #cfd7e5;
  fill-opacity: 0.4;
  shape-rendering: crispEdges;
}
```


``` nv.d3.js

// brushオブジェクト    
var callback = function(){
  console.log("bashend");
  var brushElem = d3.select('.extent');
  var _x = new Number(brushElem.attr('x'));
  var _y = new Number(brushElem.attr('y'));
  var _width = new Number(brushElem.attr('width'));
  var _height = new Number(brushElem.attr('height'));

  // xMin,xMax等設定＆選択範囲内のデータ抽出。
  var xMin = 0;
  var xMax = 0;
  var yMin = 0;
  var yMax = 0;
  var newState = {"disabled":[]};

  //選択範囲が0じゃない場合
  if (_width != 0 && _height != 0){
    xMin = x.invert(_x);
    xMax = xMin + x.invert(_width);
    yMin = y.invert(_y + _height);
    yMax = y.invert(_y);
    data = data.filter(function(row,i){
      var rowX = row["values"][0]["x"];
      var rowY = row["values"][0]["y"];
      if(xMin <= rowX && rowX <= xMax && yMin <= rowY && rowY <= yMax){
        newState.disabled[i] = false;
        row.disabled = false;
        return true;
      }else{
        newState.disabled[i] = true;
        row.disabled = true;
        return false;
      }
    });
  }else{
    //単純なクリックの場合、スケールを戻す
    data = data.filter(function(row,i) {
      row.disabled = false;
      newState.disabled[i] = false;
    });
  }

  state.disabled = newState.disabled;

  //グラフ再描画
  dispatch.stateChange(state);
  chart.update();

  //選択範囲を非表示に
  d3.select('.extent').attr({width: 0, height: 0, x: 0, y: 0});
};

var _brush  = d3.svg.brush()
  .x(x) // x軸を選択可能範囲に指定
  .y(y).on('brushend',callback);

var wrapEnter = d3.select(".nv-groups");
wrapEnter.call(_brush).selectAll("rect");
```

## ちょっとだけ解説

まず範囲指定するためにd3.svg.brush()を使用しています。
d3.svg.brush()を使用すると「rect」タグが自動で作られクラス名が「extent」になっています。

```
<rect class="extent" x="0" width="0" y="0" height="0" style="cursor: move;"></rect>
```

範囲指定後の「bashend」のイベントを拾って「extent」のx,y,width,heightを取得し、範囲内のデータをfilterで絞り込み表示するようにしています。

次にドットを表示する、しないについて、NVD3の散布図「nv.models.scatterChart」にはlegend（凡例）を表示する機能があり、凡例をクリックした際に、凡例と同じグループのドットを表示する、しないを指定できる機能があり、今回はそちらを利用しました。

具体的に言うと、
もともとの散布図のデータはこうなっています。

```
[{key: グループ名　※legend（凡例）として表示される,
values:[{
		 x: x軸の位置
      , y: y軸の位置
      , size: 円の大きさ
      , shapes: '円の形'
}]}]

```

再描画時にドットを表示しない場合は「disabled:true」を追加します。


```
[{disabled: true,
key: グループ名　※legend（凡例）として表示される,
values:[{
		 x: x軸の位置
      , y: y軸の位置
      , size: 円の大きさ
      , shapes: '円の形'
}]}]
```


さらにstate.disabledというオブジェクトにgroupの数true,falseの配列が入っているので、表示したくないgroupと同じ配列番号にtrueを入れて上げ、
以下のメソッドを実行すると再描画されます。

```
dispatch.stateChange(state);
chart.update();
```

##まとめ

d3.js単体で作った方が処理速度は早い気がしますが、
nvd3は便利な関数が多いので好きです。

散布図にドラッグでドリルダウン機能はすごく欲しかったので、
できてよかったです。

ソースはアップしているので、
よかったら使ってみてください。