D3.js v4/v5 force simulation リンク作用力 – サンプル
D3.jsのforceSimulation(旧force layout)のプログラムデモです(v4/v5対応)。リンクによる相互作用を説明します。ノード間の相互作用についてはこちらを参照ください。
ノード(丸いやつ)をドラッグしてください
サンプルコード
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 v5 force simulation node detail</title> </head> <body> <svg width="800" height="600"></svg> <script src="https://d3js.org/d3.v5.min.js"></script> <script> // 1. 描画用のデータ準備 var width = document.querySelector("svg").clientWidth; var height = document.querySelector("svg").clientHeight; var nodeNumber = 30; var nodesData = []; for(var i = 0; i < nodeNumber; i++) { nodesData.push({ "index": i, "x": width * Math.random(), "y": height * Math.random(), "r": 10 }); } var linksData = []; var i = 0; for(var j = i + 1; j < nodeNumber; j++) { linksData.push({ "source": i, "target": j, "l": Math.random() * 150 + 5 + nodesData[i].r + nodesData[j].r }); } // 2. svg要素を配置 var link = d3.select("svg") .selectAll("line") .data(linksData) .enter() .append("line") .attr("stroke-width", 1) .attr("stroke", "black"); var node = d3.select("svg") .selectAll("circle") .data(nodesData) .enter() .append("circle") .attr("r", function(d) { return d.r }) .attr("fill", "LightSalmon") .attr("stroke", "black") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); // 3. forceSimulation設定 var simulation = d3.forceSimulation() .force("link", d3.forceLink() .distance(function(d) { return d.l; }) .strength(0.03) .iterations(16)) .force("collide", d3.forceCollide() .radius(function(d) { return d.r; }) .strength(0.7) .iterations(16)) .force("charge", d3.forceManyBody().strength(-200)) .force("x", d3.forceX().strength(0.02).x(width / 2)) .force("y", d3.forceY().strength(0.02).y(height / 2)); simulation .nodes(nodesData) .on("tick", ticked); simulation.force("link") .links(linksData) .id(function(d) { return d.index; }); // 4. forceSimulation 描画更新用関数 function ticked() { 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; }); } // 5. ドラッグ時のイベント関数 function dragstarted(d) { if(!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if(!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } </script> </body> </html> |
解説
1. 描画用のデータ準備
1 2 3 4 5 6 7 8 9 10 11 12 |
var width = document.querySelector("svg").clientWidth; var height = document.querySelector("svg").clientHeight; var nodeNumber = 30; var nodesData = []; for(var i = 0; i < nodeNumber; i++) { nodesData.push({ "index": i, "x": width * Math.random(), "y": height * Math.random(), "r": 10 }); } |
document.querySelector( )でsvgの幅と高さを取得します。
リンクをつなぐノードのIDは、任意のkey名を指定することができます。今回はindexをノード用データ配列に格納して後で指定します。数字でなく文字列でもID指定できます。
1 2 3 4 5 6 7 8 9 |
var linksData = []; var i = 0; for(var j = i + 1; j < nodeNumber; j++) { linksData.push({ "source": i, "target": j, "l": Math.random() * 150 + 5 + nodesData[i].r + nodesData[j].r }); } |
リンク用配列データlinksData の作成です。今回はリンクの長さをリンクごとに変えたいので l を定義しておきます。
2. svg要素を配置
リンクとノードのsvg要素を配置していきます。call( )でドラッグ時のイベント関数を登録しています。
3. forceSimulation設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var simulation = d3.forceSimulation() .force("link", d3.forceLink() .distance(function(d) { return d.l; }) .strength(0.03) .iterations(16)) .force("collide", d3.forceCollide() .radius(function(d) { return d.r; }) .strength(0.7) .iterations(16)) .force("charge", d3.forceManyBody().strength(-200)) .force("x", d3.forceX().strength(0.02).x(width / 2)) .force("y", d3.forceY().strength(0.02).y(height / 2)); simulation .nodes(nodesData) .on("tick", ticked); simulation.force("link") .links(linksData) .id(function(d) { return d.index; }); |
forceSimulationで設定できるリンクによる相互作用(ハイライト部分)を解説します。
ノード間の相互作用については、こちらを参照ください。
“link” : リンクによる相互作用
1 2 3 4 5 |
.force("link", d3.forceLink() .distance(function(d) { return d.l; }) .strength(0.03) .iterations(16)) |
distance | リンクの長さを設定します。リンクは常に設定された長さに戻ろうとします。いわゆる自由長です。デフォルトは30。 |
strength | リンクの強度です。デフォルトは1 / Math.min(count(link.source)、count(link.target))。ひとつずつのノードとつながっている場合は1です。同じノード間に2つのリンクがあっても反力が同じになる仕様です。 |
iterations | simulationの反復回数。反復回数を増やすと計算が安定しますが計算時間が増加します。デフォルトは1。 |
1 2 3 |
simulation.force("link") .links(linksData) .id(function(d) { return d.index; }); |
id( )でインデックスにするkey名を指定します。デフォルトはnode.indexです。
4. forceSimulation 描画更新用関数
シミュレーションのステップごとに呼び出される関数です。svg要素を動かすために計算結果をsvg要素の位置に反映します。
5. ドラッグ時のイベント関数
ドラッグ時のイベント関数です。こちらを参照ください。
まとめ
ここではリンクによる相互作用をまとめました。次に座標更新のアルゴリズムとシミュレーションの実行関数を紹介します。