d3.forceLinkがオブジェクト要素間のバネの伸縮効果の再現
- d3.forceにある一つ重要な要素d3.forceLinkよりバネの伸縮効果を生み出すことができます
- 特にドラッグ/ドロップ併合に使用すると、よりバネの伸縮効果の体験はできます
- d3.forceLinkはsvgのlineエレメントでLinkを描画します
- d3.forceSimulationのtickイベントより、動的にオブジェクト間の距離を計算しながら、lineを描画します
- d3.forceLinkのバネ効果の強さはcollision/charge/centerなどの要素の設定より調整されます
作成要点1:オブジェクト要素のリンク元(source)ターゲット先(target)設定
- 描画のオブジェクトデータセットを定義します
- オブジェクト間にリンクをつけるターゲット先にのデータセットを定義します
// 描画するオブジェクトのデータセット data_set = [{name:"aaa"}, {name:"bbb"}, {name:"ccc"}, {name:"ddd"}, {name:"eee"}, {name:"fff"} ] ; // オブジェクト間のリンク元とリンク先定義 link_set = [ {source:"aaa", target:"bbb"}, {source:"bbb", target:"ccc"}, {source:"ccc", target:"ddd"}, {source:"ddd", target:"eee"}, {source:"eee", target:"fff"}, {source:"fff", target:"aaa"}, ];
- リンク元: sourceで定義
- リンク先: target で定義
- 「source」、「target」は決まりキーワード、変えることはできません
作成要点2:d3.forceSimulationでd3.forceLinkを定義
- d3.forceSimulationでforceのオブジェクト描画定義時にd3.forceLinkを定義します
var line_force = d3.forceSimulation() .nodes( data_set ) // オブジェクトのデータセット .on("tick", ticked) .force("link", d3.forceLink(link_set) // リンク元とリンク先のデータセット .id(function(d){ return d.name ; }) // オブジェクトデータセットにあるリンク要素フィールド設定 .distance(20) ) .force("center", d3.forceCenter(width/2, height/2)) .force('charge', d3.forceManyBody().strength(15)) .force("collision", d3.forceCollide(15)) // オブジェクトのぶつけ合い距離 ;
- d3.forceLink().id()でのidがリンク元/先にのデータフィールド名を定義
作成要点3:d3.tickイベントのリスナーでリンクの描画要素(line)の始点/終了設定
- d3.forceの描画設定はd3.tickイベントリスナーの処理で実現します
- d3.forceLinkはsvgの要素(line)の始点/終点を定義して描画します
function ticked(){ circles .attr("cx", function(d){ return d.x; }) .attr("cy", function(d){ return d.y; }) ; links // svgのLineの始点/終点設定 .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; }); }
- 上記例の全コードは以下のようです
data_set = [ {name:"aaa"}, {name:"bbb"}, {name:"ccc"}, {name:"ddd"}, {name:"eee"}, {name:"fff"} ] ; link_set = [ {source:"aaa", target:"bbb"}, {source:"bbb", target:"ccc"}, {source:"ccc", target:"ddd"}, {source:"ddd", target:"eee"}, {source:"eee", target:"fff"}, {source:"fff", target:"aaa"}, ]; var width=600, height=300; var force_g = d3.select("#content").append("g") ; var links = force_g.selectAll(".link") .data(link_set) .enter() .append("line") .attr("class", "link") .attr("stroke", "#000") .attr("stroke-weight", 1) ; var circles = force_g.selectAll("circle") .data(data_set) .enter() .append("circle") .attr("class","test-circle") .attr("r", 5) .attr("fill","lightblue") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended) ) ; var line_force = d3.forceSimulation() .nodes(data_set) .on("tick", ticked) .force("link", d3.forceLink(link_set) .id(function(d){ return d.name ; }) .distance(20) ) .force("center", d3.forceCenter(width/2, height/2)) .force('charge', d3.forceManyBody().strength(15)) .force("collision", d3.forceCollide(15)) ; function dragstarted(d) { if (!d3.event.active) line_force.alphaTarget(0.9).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) line_force.alphaTarget(0); d.fx = null; d.fy = null; } function ticked(){ circles .attr("cx", function(d){ return d.x; }) .attr("cy", function(d){ return d.y; }) ; links .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; }); }