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

JavaScriptのビジュアライゼーションライブラリの一つである、D3.js、今回は機能の一つである【forcesimulation】を用いて簡単なビジュアライゼーションを作ってみたいと思います。HTML含め初心者にもわかりやすく解説していくので、是非みてみてください。
D3.js(バージョン4)のforcesimulationで”動く”ビジュアライゼーションを作ってみましょう。
※注意:説明のため大きな表を扱います。スマホの方は大変恐れ入りますが、PC、またはタブレットを使用してご覧ください。
【node編】ということで、ここではnodeに関する基本の機能しか使っていきません。まずは以下のフォルダ構成を用意しましょう。
今回作成する簡単なビジュアライゼーションくらいならHTML1つで収められるのですが、ここではあえて分けています。今後拡張していく時、ファイルごとに役割が分かれているととても楽だからです。
ここから先はHTMLやD3.jsを使ったJavaScriptのソースについての話になりますが、内容通りに実装したらどんな”動く”ビジュアライゼーションが出来上がるのかを先にみておきましょう!
カラフルなサークルがひしめきあって綺麗ですね!サークルは1個1個動かすことができます。他のサークルに干渉しない設定、中心に集まってくる設定などもforcesimulationで定義できるのです。
それでは、実際に実装するために1ファイルずつ見ていきましょう。
HTMLで表示用の領域を作ろう
まずはHTMLにD3.jsで作成するグラフをブラウザで表示するための領域を追加していきます。また、ここで必要となるJavaScriptライブラリ(D3.js)やソースの読み込みも行います。以下のように作成しましょう。
<html>、<head>、<body>タグが定義されていますね。これらはページを表示させるのに必要最低限なタグなので説明は割愛させていただきます。ここでは別で定義されている部分について見ていきます。
1 |
<svg height=960 width=1000 ></svg> |
ビジュアライゼーションを描画するための領域としてSVG領域を定義しています。この領域がなければ図を表示させることすらできないので忘れずに定義しておきましょう。(今回はHTMLで定義していますが、JavaScriptで動的に定義しても構いません)
SVGとは?
PNGやJPEGのように画像の拡張子として使われることが多いですが、先の2つはビットマップ形式で表現されているのに対し、SVGはベクトル形式で記述されたデータになります。主な利点として拡大しても画像が荒くならないことやアニメーション、ぼかしなどのフィルタをかけやすいことがあげられます。
1 2 |
<script src="https://d3js.org/d3.v4.min.js"></script> <script src="d3-test.js"></script> |
ここでは今回のメインとなるライブラリ、D3.jsのソースとその動きを記述したjsソース(d3-test.js)を読み込む指定をしています。HTMLでJavaScriptのソースを読み込ませる方法についての詳細は以下の記事をご参考ください。
これでページ読み込み用HTMLの準備は終わりました。次はD3.jsで”動く”ビジュアライゼーションを作成していきましょう。
JavaScript&D3.jsで”動く”ビジュアライゼーションを作成しよう
次に、JavaScriptとD3.jsを用いて”動く”ビジュアライゼーションを作成していきます。ここで鍵となるのがD3.jsのforcesimulationという機能です。以下のソースを見てください。
1部分ずつ見ていきます。
描画データの作成(node)
1 2 3 |
//ノードのデータを作成 var range = 100; var nodes = d3.range(0, range).map(function(d,i){ return {id:i,label: "l"+d ,r:~~d3.randomUniform(5, 60)()}}); |
ここではビジュアライゼーションの描画に必要なデータを作成しています。forcesimulationで描画するデータはnode(ノード)と呼ばれるので覚えておきましょう。
nodesは100個のnodeからなる配列で、node1つ1つはオブジェクト(”{ id:~~ , r:~~ }”のように表現されるもの)です。今回のnodeは以下のような情報を持っています。
- id : nodeを識別するid
- r : nodeの大きさを決める(後に説明しますが、nodeの図形は円で表します。そのためこのrはradius(半径)という意味で捉えてください)
d3.rangeや.map、d3.randomUniform等のメソッドはとりあえず覚えないで構いません。
ひとまず100個のnodeがこれから表示されるんだな〜と楽しみにしておいてください。
forcesimulationの設定
1 2 3 4 5 6 7 8 9 10 11 |
//メイン!D3.jsのforcesimulation //svg領域の大きさを定義する var svgheight = 960, svgwidth = 1000; var simulation = d3.forceSimulation() .force("collide",d3.forceCollide().radius(function(d){return d.r;}).iterations(16) ) //衝突値の設定 .force("charge", d3.forceManyBody()) //反発力の設定 .force("center", d3.forceCenter(svgwidth / 2, svgheight / 2)) //svg領域の中心を重力の中心とする設定 .force("x", d3.forceX().x(svgwidth / 2).strength(0.2)) //x方向に戻る力 .force("y", d3.forceY().y(svgheight / 2).strength(0.2)) //y方向に戻る力 ; |
続いてはメインとなるforcesimulationの設定です。ここでnodeに働く力を定義して、”動く”ビジュアライゼーションを作っていきます。前半のsvgheight,svgwidthはHTMLで定義したSVG領域の大きさを変数として定義しているだけです。
今回は5つの力を定義しています。それぞれ見ていきましょう。少し量が多いですが頑張ってください!
機能 | 機能名 | 機能説明 | 設定値 | 設定値説明 |
---|---|---|---|---|
d3.forceCollide() | 衝突反発力 | node間が重なろうとした時の反発力を設定します。これがなければnodeは重なり放題です。 | radius(function(d){return d.r;}) | radius(半径)、反発力の適応範囲を設定します。10なら半径10以内には誰も近づけさせん!ということです。この場合では先ほど作成したnode1つ1つの大きさ(r)を指定しています。function(d)のdはnode1つ1つが順番に代入されていきます。 |
iterations(16) | この値が大きければ大きいほど反発力の計算量が多くなり正確に反発されます。 | |||
d3.forceManyBody() | 斥力 (クーロン力) |
node間が引き合う力を設定します。デフォルトではマイナス値なので離れる力が働きます。 | strength(-30) | クーロン力の強さです。-30がデフォルト値なので離れますがプラスにすると近くに寄るようになります。他の力とのバランスを気をつけてください。最悪爆発します。 |
d3.forceCenter( svgwidth / 2, svgheight / 2) |
重力中心点 | node全体の”描画時”の中心を設定します。あくまで”描画時”なので、その後の復帰力は下項目のd3.forceX,Yだということに注意してください。 | – | – |
d3.forceX() | x軸復帰力 | nodeが動いた時に指定した”x軸”への復帰値を設定します。”座標”ではなく”x軸”ということに注意してください。(つまりd3.forceYがない場合は縦軸(x軸)にnodeが集中します。 | x(svgwidth / 2) | 復帰先のx軸を設定します。今回はSVG領域の横幅の半分、つまり中央の軸を指定しています。 |
strength(0.2) | 復帰力の強さを設定します。値が大きければ大きいほど強くなり、マイナスになると挙動がおかしくなるので注意してください。 | |||
d3.forceY() | y軸復帰力 | nodeが動いた時に指定した”y軸”への復帰値を設定します。注意点についてはd3.forceXと同様です。 | y(svgheight / 2) | 復帰先のy軸を設定します。今回はSVG領域の縦幅の半分、つまり中央の軸を指定しています。 |
strength(0.2) | 復帰力の強さを設定します。注意点についてはd3.forceXと同様です。 |
大きな表になってしまいました。しかしまだ書ききれない部分もあるので、もう少し詳細を知りたい方は以下のDocumentをご参考ください。
https://github.com/d3/d3-force ※英語です。
設定の有無による違いを見る上で一番いいのはコメントアウトすることです。機能を知りたい設定値の有無による違いを見ることができます。ドキュメントなどを読むことも大事ですが、実際に動かしながら覚える方が記憶や理解が早くなると思います。
さて、大事な”動き”についての設定は完了しましたがこれだけではまだ図は描画されません。続いての部分に参りましょう。
nodeをSVG領域に描画する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//ノードの色を定義 var color = d3.scaleOrdinal(["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f", "#e5c494", "#b3b3b3"]); //ノードをsvg領域に描画する var node = d3.select("svg").append("g") //gはSVGの描画に必要なオブジェクトのコンテナ。aとかkとかはだめ。 .attr("class", "nodes") //gはどんな種類のオブジェクトを持つコンテナかをわかりやすく .selectAll("circle") //nodeは円、つまりcircle要素で描画する(ここでは定義されず、ただ"指定(selectAll)"されているだけにすぎない .data(nodes) //上部で定義した100個のnodeを持つ配列 .enter() //上のdataの内容を確定させる .append("circle") //ここでcircleを追加し、SVG領域に描画する .attr("class","node") //circle1つ1つがnodeというクラスであることを命名する .attr("r", function(d){ return d.r ;}) //nodeの大きさ(半径)を指定する .attr("fill",function(d){ return color(d.id);}) //nodeの色を指定する。colorは上部で定義されている .call(d3.drag() //nodeをマウスでドラッグした時に通る関数を定義。関数の内容は後ほど .on("start", dragstarted) .on("drag", dragged) .on("end", dragended) ) ; |
ここでnodeをSVG領域に描画します。上部のcolorはなくてもいいのですが、せっかく”動く”おしゃれなビジュアライゼーションを作ろうとしているのですからカラフルにしよう!ということで定義しています。d3.scaleOrdinalがどんな機能を持つかまでは考えなくてもいいです。
「node =」からはnodeの描画です。「select(“svg”)」でSVG領域を選んで、「.append(g)」でg要素(aとかkはだめです)を追加します。基本コメントアウトで書いてある通りです。「.attr(“class”~」と書いてある箇所が2つありますが、これは後に機能追加したりCSSで指定するための“クラス”という重要な概念なので、忘れずにしっかり定義しておきましょう。要は種類のラベルを貼っておくということです。
nodeをマウスで動かす
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//ノードをマウスドラッグで動かすための関数 function dragstarted(d) { //ドラッグ開始時の関数 if (!d3.event.active) simulation.alphaTarget(.03).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(.03); d.fx = null; d.fy = null; } |
前節でnodeを定義した際に、「.call(d3.drag()~~」という記述があったと思います。あそこではnodeがマウスドラッグされた際にどのような挙動をするのかを関数で指定していました。ここではその関数の中身を記述しています。
ドラッグ開始時、ドラッグ中、ドラッグ終了時と3つの関数を定義しています。
「d」とはマウスドラッグの対象nodeです。3つの関数全てでd.fx(fy)を扱っていますが、これは固定(fixed)座標のことで設定された値でnodeを固定するという意味です。これを設定したnodeはforcesimulationの影響から外れ、完全に座標が固定されます。ん?固定してしまったらドラッグで動かせないじゃん、と思うかもしれません。
真ん中の関数ではマウスドラッグの座標を取得して、それを固定座標に代入しています。
つまり
ドラッグ状態でマウスを動かす→固定座標にマウスの座標が代入される→マウスを動かす→固定座標に・・・
を繰り返すことでnodeが動くという仕組みです。
ドラッグ終了時に固定座標にnullを代入することによって、再びforcesimulationの影響下に戻します。
“if (!d3.event.active) simulation.alphaTarget(.03).restart();”
forcesimulationは重力計算しているだけあって、そこには加速度などものパラメータもあります。もし動きが止まって完全に静止状態だった時に「ほらほら動けー」と喝を入れる役割がこの1行にはあります。
さて、次節でいよいよ最後です・・・。
forcesimlationスタート!
1 2 3 4 5 6 7 8 9 10 |
//動作開始! simulation .nodes(nodes) .on("tick", ticked); function ticked() { node .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); } |
nodeを作成し描画し、forcesimulationで”動き”の設定もしました。いよいよ動かしていきます。
「.nodes(nodes)」はnode達を設定したforcesimulationの影響下に加えるという記述です。
これがなければnodeは一切動かないので注意してください。
「.on(“tick”,ticked)」はforcesimulationで設定された値を元に、時間経過でどのような動きをするかということを定義しています。
これもなければnodeは一切動かないので注意してください。
tickedは関数で、その内容は下に記述されています。ここでは単純にnode(描画する時に定義したSVGのcircle要素のnodeのことで、nodes配列の1要素ではない!)の位置(circle要素の位置はcx,cyで決まる)は、forcesimulationによって計算された値であるd.x(y)をそのまま使ってねと定義しています。
紛らわしいのですが、画面上に描画されているnode(SVGのcircle要素)と内部でforcesimulationによって座標情報(d.xやd.fy)などを計算されるnode(nodes配列の1要素)は繋がってはいますが別物であることに注意してください。
node(SVGのcircle要素)は裏でnode(nodes配列の1要素)に操られていて、そのボスはforcesimulationだ
と考えればわかりやすいかもしれません。
動かしてみよう!
これで簡単な”動く”ビジュアライゼーションが完成しました!さぁ、後は動かしてみるだけです!!
forcesimulationの各設定を変えたり、
コメントアウトして消して各設定の有無による違いを見てみたり、
circleの大きさ(d.r)を変化させてみたり
と色んなことをしてみてください!
D3.jsのforcesimulationにはまだまだ沢山の機能があります(今回はネットワークグラフに必須なlink(線)さえ紹介していません)。今後ノウハウとして少しずつ紹介していきますが、一番いいのは自身の手で動かしながら理解していくことです。
少しでも先に進んで見たいと思った方は、当ブログのD3.jsタグを見るか以下のURLから様々な美しい例を見て様々な使い方、技術を学んで見てください。
https://github.com/d3/d3/wiki/Gallery
D3.jsのforcesimulationを詳しく解説!【node編】については以上です。
続編の【link編】は以下になります。よろしければご覧ください。