D3.js v4/v5 force simulation ノードに複数のsvg要素を含める方法
d3.drag svg要素の同時ドラッグ (v4/v5対応)
d3.jsでノードに複数のsvg要素を含め、テキストなどの要素を同時にドラッグする方法を解説します。
サンプルデモ
サンプルコード
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 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>D3 v5 force simulation group element</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 nodesData = []; for(var i = 0; i < 50; i++) { nodesData.push({ "x": width * Math.random(), "y": height * Math.random(), "r": 40 * Math.random() + 5 }); } // 2. svg要素を配置 var nodeGroup = d3.select("svg") .selectAll("g") .data(nodesData) .enter() .append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); nodeGroup.append("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("r", function(d) { return d.r }) .attr("fill", "Gold") .attr("stroke", "black") .append("title") .text("This is title."); nodeGroup.append("text") .attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }) .attr("text-anchor", "middle") .attr("dominant-baseline", "middle") .style("fill", "steelblue") .text("Ball") .append("title") .text("This is title."); // 3. forceSimulation設定 var simulation = d3.forceSimulation() .force("collide", d3.forceCollide() .radius(function(d) { return d.r + 1 })) .force("charge", d3.forceManyBody()) .force("x", d3.forceX().strength(0.05).x(width / 2)) .force("y", d3.forceY().strength(0.05).y(height / 2)); simulation .nodes(nodesData) .on("tick", ticked); // 4. forceSimulation 描画更新用関数 function ticked() { nodeGroup.select("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); nodeGroup.select("text") .attr("x", function(d) { return d.x; }) .attr("y", 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> |
解説
本サンプルプログラムはforceSimulationを使っています。forceSimulationの詳細についてはこちらを参照ください。
プログラムのハイライト部分を説明します。
1 2 3 4 5 6 7 8 9 |
var nodeGroup = d3.select("svg") .selectAll("g") .data(nodesData) .enter() .append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)); |
まず、グループ要素を表す"g"要素を作成します。"g"要素にノード用のデータ配列を割り当て、ドラッグイベントを登録します。
その後、"g"タグの子要素として"circle"と"text"を設定します。"g"要素にノードのデータ配列が割り当てられているので、参照して使います。さらに"circle"と"text"の子要素として"title"も設定可能です。サンプルデモのノードのにカーソルを合わせるとtitleの文字列が表示されます。(※タブレットやスマートフォンでは表示されません。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
nodeGroup.append("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .attr("r", function(d) { return d.r }) .attr("fill", "Gold") .attr("stroke", "black") .append("title") .text("This is title."); nodeGroup.append("text") .attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }) .attr("text-anchor", "middle") .attr("dominant-baseline", "middle") .style("fill", "steelblue") .text("Ball") .append("title") .text("This is title."); |
"text"のスタイルとして設定している"text-anchor"は横方向のテキスト位置で、"start"、"middle"、"end"が設定できます。"dominant-baseline"は縦方向の位置です。
また、forceSimulationのステップ更新時にsvg要素の位置を次のように変更します。
1 2 3 4 5 6 7 8 9 |
// 4. forceSimulation 描画更新用関数 function ticked() { nodeGroup.select("circle") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); nodeGroup.select("text") .attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); } |
forceSimulationを用いず、Dragだけの動作の場合は、イベント関数を下記のように変更します。
1 2 3 4 5 6 7 8 |
function dragged(d) { d3.select(this).select("circle") .attr("cx", d.x = d3.event.x) .attr("cy", d.y = d3.event.y); d3.select(this).select("text") .attr("x", d.x = d3.event.x) .attr("y", d.y = d3.event.y); } |
まとめ
わかってしまえば簡単です。クラスター(円のかたまり)を作ったり、オブジェを作ったりいろいろ応用ができそうです。