D3.js の force layout を試してみる
概要
- D3.js で Force layout を動かしてみる。
- 理解用に簡単なサンプルを作る。
- その他、オプションを試してみる。
- ラベル表示と矢印を追加。
情報
D3.js って…?
- 日本語サイト
- D3 = Data Driven Document
- データに基づいてドキュメント (要は DOM) を操作するための Javascriptライブラリ。
- svg を使った華麗なグラフのデモが目立つが、DOM操作のライブラリとしても優れている (と、使ってみて思った)。
Force layout
- force = 『力』とか『エネルギー』とか。
- スターウォーズのアレ??
- 要素同士が影響し合っている状態を node (円) と link (線) で表している。
- 説明よりもサンプル見た方が早い。
作ったサンプル
- jsdo.it 上に置きました。
- 作り方とかは以下、詳細に。
簡単なサンプル作ってみる
データ
- 『AはBを好き』というデータを適当に作ってみた。
No. | 名前 | 性別 | 好き (No.) |
---|---|---|---|
1 | 蓮 | 男 | 陽菜 (6) |
2 | 大翔 | 男 | 結菜 (8) |
3 | 陽向 | 男 | 葵 (9) |
4 | 陽太 | 男 | 結菜 (8) |
5 | 悠真 | 男 | 結愛 (10) |
6 | 陽菜 | 女 | 陽向 (3) |
7 | 凛 | 女 | 悠真 (5) |
8 | 結菜 | 女 | 大翔 (2) |
9 | 葵 | 女 | 悠真 (5) |
10 | 結愛 | 女 | 蓮 (1) |
- 名前は2014年の名前ランキング上位から取得。他意は無いです。
- 好き合っているのは乱数で設定。両想い分だけ手作業。
データを json 化
- Javascript で扱うにあたり、JSON形式が読みやすい。
- Force layout のサンプルに従い、手作業でJSON作成。
- data.json
{ "nodes":[ {"name": "蓮", "group": 1}, {"name": "大翔", "group": 1}, {"name": "陽向", "group": 1}, {"name": "陽太", "group": 1}, {"name": "悠真", "group": 1}, {"name": "陽菜", "group": 2}, {"name": "凛", "group": 2}, {"name": "結菜", "group": 2}, {"name": "葵", "group": 2}, {"name": "結愛", "group": 2} ], "links":[ {"source": 0, "target": 5}, {"source": 1, "target": 7}, {"source": 2, "target": 8}, {"source": 3, "target": 7}, {"source": 4, "target": 9}, {"source": 5, "target": 2}, {"source": 6, "target": 4}, {"source": 7, "target": 1}, {"source": 8, "target": 4}, {"source": 9, "target": 0} ] }
- メモ
- group は後で色分けに使用。性別で振った。
- links の source、target は nodes の並び順 (0 indexed)。
HTML、CSS、Javascript 準備
- 構成
index.html - js - main.js - css - style.css - libs - d3.v3.min.js - data - data.json
- index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"/> <title>d3.js sample</title> <!-- styles --> <link rel="stylesheet" href="css/style.css"/> </head> <body> <div class="svg_area" id="svg_area"></div> <!-- libs --> <script type="text/javascript" src="libs/d3.v3.min.js"></script> <!-- scripts --> <script type="text/javascript" src="js/main.js"></script> </body> </html>
- style.css
/*** Field ***/ .svg_area { position: relative; width: 500px; height: 500px; border: dashed 5px #000; } /*** Force layout ***/ .node { stroke: #fff; stroke-width: 1.5px; } .link { stroke: #999; stroke-opacity: .6; }
- メモ
- 表示エリアのサイズと、force layout の設定。
- main.js
/** * Initialize SVG display */ function init(svg_id, width, height, data_path) { // 色は既定のがある。 var color = d3.scale.category20(); var force = d3.layout.force() .charge(-500) // node同士の力の基準? .linkDistance(100) // node同士の距離の基準 .size([width, height]); var svg = d3.select('#' + svg_id).append('svg') .attr('width', width) .attr('height', height); d3.json(data_path, function(error, graph) { // JSON read error 時 if (graph == null) { alert(error); return; } force.nodes(graph.nodes) .links(graph.links); // Tick start force.start(); var link = svg.selectAll('.link') .data(graph.links).enter() .append('line') .attr('class', 'link') .style('stroke-width', 1); var node = svg.selectAll('.node') .data(graph.nodes).enter() .append('circle') .attr('class', 'node') .attr('r', 5) .style('fill', function(d) { return color(d.group); }) .call(force.drag); force.on('tick', function() { link.attr('x1', function(d) { return d.source.x; }) .attr('y1', function(d) { return d.source.y; }) .attr('x2', function(d) { return d.target.x; }) .attr('y2', function(d) { return d.target.y; }); node.attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); }); }); } /** * Main */ var svg_id = 'svg_area'; var element = document.getElementById(svg_id); var data_path = 'data/data.json'; init(svg_id, element.clientWidth, element.clientHeight, data_path);
- メモ
- force layout は「d3.layout.force()」にデータ与えれば自動生成してくれる。便利!!
- tick 動かして Update 処理とか、JSON の形式とか、色々制約はあるけど…。
- force layout は「d3.layout.force()」にデータ与えれば自動生成してくれる。便利!!
オプション表示
- ラベル、矢印を追加してみる。
- 参照
- 以降、コードは主要部分のみ。詳しくは jsdo.it に上げたもの参照のこと。
ラベル付与
- css.style に text のスタイル追加
.node text { font: 16px helvetica; }
- main.js (変更主要部分)
d3.json(data_path, function(error, graph) { // 略 var node = svg.selectAll('.node') .data(graph.nodes).enter() .append('g') .attr('class', 'node') .call(force.drag); var circle = node.append('circle') .attr('r', 5) .style('fill', function(d) { return color(d.group); }); var text = node.append('text') .attr('dx', 10) .attr('dy', '.35em') .text(function(d) { return d.name; }) .style('stroke', 'gray'); force.on('tick', function() { // 略 (link 位置更新) circle.attr('cx', function(d) { return d.x; }) .attr('cy', function(d) { return d.y; }); text.attr('x', function(d) { return d.x; }) .attr('y', function(d) { return d.y; }); }); });
- メモ
- 元は '.node' 直下に circle 作っていたが、変更。
- '.node' 直下に 'g' 作って、node 属性複数個。
- その下に 'circle' と ’text' 付加
- tick 時処理も circle と text 両方更新
- 元は '.node' 直下に circle 作っていたが、変更。
矢印追加
- source から target に向かう矢印表示
- svg の defs に矢印表示を定義し、それを各 link に当てる形の様だ。
- main.js (主要部分)
function _setArrow(svg) { svg.append('defs').selectAll('marker') .data(['arrow']).enter() .append('marker') .attr('id', function(d) { return d; }) .attr('viewBox', '0 -5 10 10') .attr('refX', 25) .attr('refY', 0) .attr('markerWidth', 10) .attr('markerHeight', 10) .attr('orient', 'auto') .append('path') .attr('d', 'M0,-5L10,0L0,5 L10,0 L0, -5') .style('stroke', '#4679BD') .style('opacity', '0.6'); }; function init(svg_id, width, height, data_path) { // 略 var svg = d3.select('#' + svg_id).append('svg') .attr('width', width) .attr('height', height); _setArrow(svg); d3.json(data_path, function(error, graph) { // 略 var link = svg.selectAll('.link') .data(graph.links).enter() .append('line') .attr('class', 'link') .style('stroke-width', 1) .style('marker-end', 'url(#arrow)'); // 略 }); }