D3.js v4/v5 force simulation 最小構成 – サンプル
D3 forceSimulationのプログラムデモです。
forceSimulationの最も簡単な構成のプログラムを紹介します。はじめてforceSimulationを使う際などに活用ください。
サンプルデモ – ノードをドラッグしてください。
D3.jsのforceSimulationは、ノードの位置をばね力などを考慮して計算するものです。v3以前ではforce layoutとして定義されていて、同じ関数が使えないので書き換える必要があります。設定できるパラメータがいくつもありますが、ここではまず導入できることを目指して最小構成のデモとプログラムを解説します。
サンプルコード
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 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 v5 force simulation</title> </head> <body> <svg width="400" height="300"></svg> <script src="https://d3js.org/d3.v5.min.js"></script> <script> // 1. 描画用のデータ準備 var nodesData = [ {}, {}, {}, {}, {}, {} ] var linksData = [ { "source": 0, "target": 1 }, { "source": 1, "target": 4 }, { "source": 2, "target": 3 }, { "source": 2, "target": 5 }, { "source": 5, "target": 1 } ] // 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", 7) .attr("fill", "LightSalmon") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); // 3. forceSimulation設定 var simulation = d3.forceSimulation() .force("link", d3.forceLink()) .force("charge", d3.forceManyBody()) .force("center", d3.forceCenter(200, 150)); simulation .nodes(nodesData) .on("tick", ticked); simulation.force("link") .links(linksData); // 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 13 14 15 16 |
var nodesData = [ {}, {}, {}, {}, {}, {} ] var linksData = [ { "source": 0, "target": 1 }, { "source": 1, "target": 4 }, { "source": 2, "target": 3 }, { "source": 2, "target": 5 }, { "source": 5, "target": 1 } ] |
nodesDataはノードのデータです。ただ描画するだけであれば空配列でOKです。リンク(linksData)は、つなぐ2つノードのID(source,target)が必要です。
2. svg要素を配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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", 7) .attr("fill", "LightSalmon") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); |
リンクとノードのsvg要素を配置していきます。call()でドラッグ時のイベント関数を登録しています。これによりノードのドラッグが可能になります。
3. forceSimulation設定
いよいよここからがforceSimulationの設定です。
1 |
d3.forceSimulation().force("link", d3.forceLink()) |
リンクによるバネ力を動作させます。
1 |
.force("charge", d3.forceManyBody()) |
ノード間のクーロン力を動作させます。デフォルトでは反発力が設定されます。この行を省略してもforceSimulationは動作します。
1 |
.force("center", d3.forceCenter(200, 150)); |
全ノードの中心位置を設定します。無くても動作しますが、無いと無重力状態になり画面の外に要素が飛び出ても帰ってきません。
1 2 3 |
simulation .nodes(nodesData) .on("tick", ticked); |
シミュレーションにノード用のデータ配列を登録します。.on(‘tick’, )では計算の更新ごとに呼び出す関数を登録します。計算結果はノード用のデータ配列に書き出される仕様になっており、svg要素を動かすために計算結果をsvg要素の位置に反映する必要があります。
1 2 |
simulation.force("link") .links(linksData); |
シミュレーションにリンク用のデータ配列を登録します。
4. forceSimulation 描画更新用関数
1 2 3 4 5 6 7 8 9 10 |
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; }); } |
上で設定した関数です。svg要素を動かすために計算結果をsvg要素の位置に反映します。linkのデータsourceとtargetは、最初に設定した番号ではなく、ノード用のデータ配列への参照となります。
5. forceSimulation 描画更新用関数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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; } |
ドラッグ時のイベント関数です。dに格納されているのは登録したノード用のデータです。ノード用のデータにfx, fyが定義されている場合、そのノードの座標が固定されます。ドラッグ中にマウスと動作を連動させるため、ドラッグ開始時にドラック要素の位置を固定し、ドラッグ中はマウス座標(d3.event.x, d3.event.y)を反映して、ドラッグ終了時に固定を解除(nullを代入)します。
また、simulationは時間がたつと停止する仕様になっているので、ドラッグ開始時にsimulationがactiveでない場合はリスタートさせます。このときに設定しているalphaTargetは、シミュレーションを滑らかに繋げるための係数で、v4から導入されたものです。v3ではリスタート時にノードがジャンプするように動いていたようです。0~1の値を設定でき、低い値の方が滑らかになりますが、0にするとリスタート時にノードが全く動かなくなります。
まとめ
このページではforceSimulationの最小構成のプログラムを掲載しました。ノード相互作用の詳細はこちら、リンク間作用についてはこちら、forceSimulation実行関数についてはこちらを参照ください。