【JavaScript】D3.jsのforcesimulationを詳しく解説!【link編】

JavaScriptのビジュアライゼーションライブラリの一つである、D3.js、今回は機能の一つである【forcesimulation】のlink(nodeを繋ぐパス)について解説していきます。興味がある方はぜひご覧ください。
D3.js(バージョン4)のforcesimulationで”動く”ビジュアライゼーションを作ってみましょう。
※注意:説明のため大きな表を扱います。スマホの方は大変恐れ入りますが、PC、またはタブレットを使用してご覧ください。
この記事には前編があり、nodeについて詳しく解説しています。この記事も解説を省略している箇所があり、その内容はその全編に書いてあるのでよろしければご覧ください。
【link編】ということで、ここではlinkに関する基本の機能しか使っていきません。まずは以下のフォルダ構成を用意しましょう。
今回作成する簡単なビジュアライゼーションくらいならHTML1つで収められるのですが、ここではあえて分けています。今後拡張していく時、ファイルごとに役割が分かれているととても楽だからです。
ここから先はHTMLやD3.jsを使ったJavaScriptのソースについての話になりますが、内容通りに実装したらどんな”動く”ビジュアライゼーションが出来上がるのかを先にみておきましょう!
赤色の丸を中心に様々な色の玉が広がっていますね。同じ色の玉はお互いに引きつけあっているように見えます。データの展開をビジュアライゼーションする時に使えそうですね。
それでは、実際に実装するためにソースファイルを見ていきましょう。
※HTMLファイルについては上記の前回の記事の内容そのままなので、解説はそちらをご覧ください。
nodeをlinkで繋ごう
ソースコードは以下のようになります。
この記事では「//——-★追加——-」とコメントアウトされている内容について見ていきます。
linkデータの作成
1 2 3 4 5 6 7 8 9 |
//linkのデータを作成// //3段階の構造にする1を中心、2~10を二段階、11~110までを三段階 var links = d3.range(1,range).map(function(d){ if(d <= 10){ return {id:d,label:"l"+d,source:0,target:d} }else{ return {id:d,label:"l"+d,source:(d-d%10)/10,target:d} } }) |
ここではnodeを繋ぐlinkのデータを作成しています。
1 |
var links = d3.range(1,range).map(function(d){ |
「d3.range(1,range).map(」は「1からrange(ここでは110)を順番に繰り返し、110個のmapオブジェクトを作成する」という意味です。
「function(d,i){」のdは上記のd3.range(1,range)に該当する1~110が順番に入ります。
この1〜110というのはnodeが持つ「id」に該当します。
1 2 3 4 5 |
if(d <= 10){ return {id:d,label:"l"+d,source:0,target:d} }else{ return {id:d,label:"l"+d,source:(d-d%10)/10,target:d} } |
link1つ1つがどのnodeとnodeを繋ぐのかということをプロパティとして持ちます、ここでのデータの構造は以下のようになっています。
id | linkを識別するためのidです。必須ではありません。 |
label | linkの名前です。必須ではありません。 |
source | そのlinkの始点です。この項目は必須で、nodeデータ作成の際に定義した”id”、もしくはnodeデータそのものを指定することができます。この例では前者です(1〜110の数字)。 |
target | そのlinkの終点です。この項目は必須で指定方法はsourceと同様です。 |
id,labelは必須ではありませんが、識別のためにあると便利です。もちろん、ほかに追加したい情報があれば追加しても構いません。
例えばlinkの長さをデータごとに指定したい、なんて時は「distance:100」みたいな項目を追加します。これを後に定義するforcesimulationのlinkで使用すればデータごとの指定が可能になります。
d3.forcesimulationの設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var simulation = d3.forceSimulation() .force("collide",d3.forceCollide().radius(function(d){return d.r;}).iterations(16) ) //-------★追加------- //linkの力学設定 .force("link", d3.forceLink().strength(1.0).distance(function(d){ if(d.id<=10){ return 200; }else{ return 20; } //-------★追加ここまで------- })) .force("charge", d3.forceManyBody().strength(-100)) .force("center", d3.forceCenter(svgwidth / 2, svgheight / 2)) .force("y", d3.forceY()) .force("x", d3.forceX()) ; |
この記述でlinkの力学設定を行います。中身を詳しく見ていきましょう。
1 |
.force("link", d3.forceLink().strength(1.0).distance( |
linkの力学設定を定義する部分はここです。「.force(“link,d3.forceLink()”」までは絶対に記述する箇所で、それ以降はlinkのオプションメソッドです。1つ1つ見ていきましょう。
strength | linkの強さを定義します。デフォルトは「1/Math.min(count(link.source),count(link.target))」となっており、node数が多ければ多いほど弱くなっていきます。弱すぎると元に戻る力が弱くなります。 |
distance | linkの長さを定義します。デフォルトは30です。 |
上記2つのオプションメソッドも実際に使ってみなければイメージがつきにくいので、値を少しずつ変えながら色々動かして見てください。
例のソースでは、distanceが以下のように定義されていると思います。
1 2 3 4 5 6 |
.distance(function(d){ if(d.id<=10){ return 200; }else{ return 20; } |
「function(d)」のdは先ほど定義したlinkのデータ1つ1つです。この処理ではd.idが10以下のlinkは200、10より上のlinkは20を返します。
今回のプログラムは中心nodeから10個のグループが離れて配置され、グループ内のnodeは近くに寄らせるという処理になっています。
もし、linkのデータ1つ1つに「distance:100」ようなプロパティを定義する場合は、以下のように書くとそれが適用されます。
1 |
.distance(function(d){ return d.distance;}) |
SVG領域にlinkを描画する
1 2 3 4 5 6 7 8 9 |
//linkをsvg領域に描画する var link = d3.select("svg").append("g") .attr("class", "links") .selectAll("line") .data(links) .enter() .append("line") .attr("stroke", "black") ; |
どれだけ頑張ってforcesimulationの設定をしても、SVG領域に描画されなければ意味がありません。この記述でlinkが描画されます。ここの詳しい解説は前回のnode編の内容と同じなので、ここでは省略させていただきます。
注意していただきたいのは、ここで定義されたlinkはlinkのデータ「links」ともforcesimulationで設定してきたlinkとも別物で、SVGのlinkオブジェクトです。ここではlinkのデータ「links」とSVGのlinkオブジェクトの対応を行いました。
forcesimulationとの対応はまだ行われておらず、このままでは動きません。
後の記述で対応づけます。
forcesimulationにlinkデータをセット&座標設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//動作開始! simulation .nodes(nodes) .on("tick", ticked); //-------★追加------- simulation.force("link") .links(links); //-------★追加ここまで------- function ticked() { node .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); //-------★追加------- 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; }); //-------★追加ここまで------- } |
linkデータを作成、forcesimulationのlink設定、SVGへの描画、色々やってきましたがこれで最後です。
ここでは作成したlinkデータをforcesimulationにセットし、力学による座標設定をします。詳しく見ていきましょう。
1 2 |
simulation.force("link") .links(links); |
ここでlinkデータをセットします。
1 2 3 4 5 |
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; }); |
先ほど述べた「SVGのlinkオブジェクトとforcesimulationの対応」はこの記述で行います。forcesimulationによって計算された座標をSVGのlinkオブジェクトにセットしています。各座標は以下のように対応しています。
SVGのlinkオブジェクト | forcesimulationで計算された座標 | 詳細 |
x1 | d.source.x | linkの始点のx座標を表します。 |
y1 | d.source.y | linkの始点のy座標を表します。 |
x2 | d.target.y | linkの終点のx座標を表します。 |
y2 | d.target.y | linkの終点のy座標を表します。 |
d.source.xってなんだ?と思う方もいるかもしれません。d.source.xの意味は
「”d(link)”の”source(始点node)”の”x(ここでは中心座標を表すcx)”」
です。つまり・・・linkの座標は始点と終点のnodeの座標を代入しているだけなのです。
動かしてみよう!
さて、これで記事の始めに載せたプログラムが実行できるようになりました。
linkのオプションを変えたり、4段階目の構造にチャレンジしてみたり、手を動かしながら色々試してみましょう。
D3.jsのforcesimulationにはまだまだ沢山の機能があります。今後ノウハウとして少しずつ紹介していきますが、一番いいのは自身の手で動かしながら理解していくことです。
少しでも先に進んで見たいと思った方は、当ブログのD3.jsタグを見るか以下のURLから様々な美しい例を見て様々な使い方、技術を学んで見てください。
https://github.com/d3/d3/wiki/Gallery
D3.jsのforcesimulationを詳しく解説!【link編】については以上です。
よろしければ他のノウハウも見ていってください。