カタカタブログ

SIerで働くITエンジニアがカタカタした記録を残す技術ブログ。Java, Oracle Database, Linuxが中心です。たまに数学やデータ分析なども。

D3.jsのForce Layout (力学モデルでグラフ描画するレイアウト)を動かしてみた

D3.jsのForce Layout (力学モデルでグラフ描画するレイアウト)を動かしてみた
D3.jsというJavaScriptでデータをきれいに可視化するライブラリに、グラフ(棒グラフとか折れ線グラフではなく、グラフ理論のグラフ。ネットワークと同義)をきれいに描画する機能があったので、試してみた。

公式サイト: https://github.com/mbostock/d3/wiki/Force-Layout

ネットで検索するともっときれいなサンプルはたくさんあるのだが。。とりあえず、htmlファイル1つだけで動作確認できる最小の構成で作ってみた。以下のソースをコピーしてindex.htmlとかに保存し、ブラウザで開くだけでグラフが描画される。

データはノードとリンクをハッシュの配列として用意するだけで、あとは以下のようなほぼお決まりのコードでレンダリングできる。

簡単なサンプルを作成する

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>D3.js(Force Layout)の練習</title>
  <style>
  </style>
</head>
<body>
  <script src="http://d3js.org/d3.v3.js" charset="utf-8"></script>
  <script>
    var w = 800;
    var h = 800;
    var nodes = [ {id:0}, {id:1}, {id:2}, {id:3} ];
    var links = [ {source:0, target:1},
                  {source:0, target:2},
                  {source:1, target:3},
                  {source:1, target:3}
                ];
   var force = d3.layout.force()
                 .nodes(nodes)
                 .links(links)
                 .size([w, h])
                 .linkStrength(0.1)
                 .friction(0.9)
                 .distance(200)
                 .charge(-30)
                 .gravity(0.1)
                 .theta(0.8)
                 .alpha(0.1)
                 .start();
    var svg = d3.select("body").append("svg").attr({width:w, height:h});
    var link = svg.selectAll("line")
                  .data(links)
                  .enter()
                  .append("line")
                  .style({stroke: "#ccc",
                          "stroke-width": 1});
    var node = svg.selectAll("circle")
                  .data(nodes)
                  .enter()
                  .append("circle")
                  .attr({r: 20,
                         opacity: 0.5})
                  .style({fill: "red"})
                  .call(force.drag);
    force.on("tick", function() {
      link.attr({x1: function(d) { return d.source.x; },
                 y1: function(d) { return d.source.y; },
                 x2: function(d) { return d.target.x; },
                 y2: function(d) { return d.target.y; }});
      node.attr({cx: function(d) { return d.x; },
                 cy: function(d) { return d.y; }});
    });
  </script>
</body>
</html>

ブラウザで開くとこんなかんじ。デフォルトの機能でマウスでノードをドラッグすることもできる。
f:id:osn_th:20141119075547p:plain

ノードにラベルテキストを付与する

上のノードにラベルを付与してみる。まずは、ノードのデータを変更しラベルを持つようにする。

    var nodes = [ {id:0, label:"A"},
                  {id:1, label:"B"},
                  {id:2, label:"C"},
                  {id:3, label:"D"}
                ];

つづいてノードの図形はcircleで追加したので、textを同じ座標で追加するため、以下のコードを追加する。

var label = svg.selectAll('text')
                    .data(nodes)
                    .enter()
                    .append('text')
                    .attr({"text-anchor":"middle",
                           "fill":"white"})
                    .style({"font-size":11})
                    .text(function(d) { return d.label; });

最後に座標計算のforce.on内でtextのx座標とy座標をcircleの座標と同じ計算ロジックで算出するようにする。

    force.on("tick", function() {
      link.attr({x1: function(d) { return d.source.x; },
                 y1: function(d) { return d.source.y; },
                 x2: function(d) { return d.target.x; },
                 y2: function(d) { return d.target.y; }});
      node.attr({cx: function(d) { return d.x; },
                 cy: function(d) { return d.y; }});
      label.attr({x: function(d) { return d.x; },
                  y: function(d) { return d.y; }});
    });

これで完成。ブラウザで開くと以下のように、ノードにラベルテキストが付与されている。
f:id:osn_th:20141119075550p:plain
最終的な構成は以下のようになった。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>D3.js(Force Layout)の練習</title>
  <style>

  </style>
</head>
<body>
  <script src="http://d3js.org/d3.v3.js" charset="utf-8"></script>
  <script>
    var w = 800;
    var h = 500;
    var nodes = [ {id:0, label:"A"},
                  {id:1, label:"B"},
                  {id:2, label:"C"},
                  {id:3, label:"D"}
                ];
    var links = [ {source:0, target:1},
                  {source:0, target:2},
                  {source:1, target:3},
                  {source:1, target:3}
                ];
   var force = d3.layout.force()
                 .nodes(nodes)
                 .links(links)
                 .size([w, h])
                 .linkStrength(0.1)
                 .friction(0.9)
                 .distance(200)
                 .charge(-30)
                 .gravity(0.1)
                 .theta(0.8)
                 .alpha(0.1)
                 .start(); 

    var svg = d3.select("body").append("svg").attr({width:w, height:h});
    var link = svg.selectAll("line")
                  .data(links)
                  .enter()
                  .append("line")
                  .style({"stroke": "#ccc",
                          "stroke-width": 1});
    var node = svg.selectAll("circle")
                  .data(nodes)
                  .enter()
                  .append("circle")
                  .attr({r: 20,
                         opacity: 0.5})
                  .style({"fill": "red"})
                  .call(force.drag);

    var label = svg.selectAll('text')
                    .data(nodes)
                    .enter()
                    .append('text')
                    .attr({"text-anchor":"middle",
                           "fill":"white"})
                    .style({"font-size":11})
                    .text(function(d) { return d.label; });

    force.on("tick", function() {
      link.attr({x1: function(d) { return d.source.x; },
                 y1: function(d) { return d.source.y; },
                 x2: function(d) { return d.target.x; },
                 y2: function(d) { return d.target.y; }});
      node.attr({cx: function(d) { return d.x; },
                 cy: function(d) { return d.y; }});
      label.attr({x: function(d) { return d.x; },
                  y: function(d) { return d.y; }});
    });
  </script>
</body>
</html>

以上。