D3.jsのforce Graphでノードを左から右へ配置するお話

あまりメインでフロントエンドの開発は行ってこなかったのですが、最近になってやっとフロントエンドの仕事もするようになってきました。
可視化ライブラリとしてD3.js(https://d3js.org/)を使っています。色々と可視化する方法があって便利です。あ、別にD3.jsにこだわりがあるわけではないのですが。

その中でも最近はForce Graph(D3 Force-Directed Graph)を使っています。(仕事で)
「ネットワーク図」と検索しても引っ掛かるみたいです。

よく目にするForce Graphは画面の中心に起点になるノードがあり、その起点ノードから放射状にノードが接続されている図かと思います。
これは起点になるノードから他のノードへどのように連結しているのかを可視化しています。

ただこのForce Graphはどのノードからどのノードへという方向性がないグラフ(無向グラフ)です。

本記事ではD3.jsのForce Graphである程度のデータの流れを可視化できるような表現方法を実現します。
具体的には起点になるノードを1番左に配置し、そこからノードを順番に右へ連結していきます。

素直にデータの流れを可視化できる(有向グラフ)のライブラリを使えよとの指摘もあるかと思いますが、そこは…また別の機会に調べます。

Contents

動作環境

Windows10 Home 64bit
D3.js version4
Chrome: 70.0.3538.77(Official Build) (64 ビット)
FireFox: 63.0.1 (64 ビット)

可視化目標

導入部分でも触れましたが「ノードを1番左に配置し、そこからノードを順番に右へ連結」します。
イメージ図は以下の通りです。

この記事で解説すること

上記の目標を達成するためのコード。
引力モデルの設定等は解説しません。
参考にさせていただいたサイト様のリンクは下部に記載いたしますので、必要があればそちらをご参照ください。

解説

HTMLの準備

body直下にidがcontentのsvg要素があることを前提とします。
widthとheightを指定していますが、スクリプトでブラウザの大きさを取得して設定しているので意味はないです。

<svg id="content" width="640" height="400"></svg>

データの準備

ここからはJavaScriptです。

data_set = [
	{name:"aaa", level:1},
	{name:"bbb", level:2},
	{name:"ccc", level:2},
	{name:"ddd", level:2},
	{name:"eee", level:3},
	{name:"fff", level:3},
	{name:"ggg", level:3},
	{name:"hhh", level:3},
	{name:"iii", level:3},
	{name:"jjj", level:3},
	{name:"kkk", level:4},
	{name:"lll", level:4}
];
link_set = [
	{source:"aaa", target:"bbb"},
	{source:"aaa", target:"ccc"},
	{source:"aaa", target:"ddd"},
	{source:"ddd", target:"eee"},
	{source:"bbb", target:"eee"},
	{source:"ccc", target:"fff"},
	{source:"ddd", target:"ggg"},
	{source:"bbb", target:"hhh"},
	{source:"ddd", target:"iii"},
	{source:"ddd", target:"jjj"},
	{source:"eee", target:"kkk"},
	{source:"jjj", target:"lll"},
];

data_set: ノードを定義
今回重要になるのがここで定義しているlevelです。
このlevelの数値に従って左から右へノードを配置します。

link_set: ノード間の関連を定義
data_setのnameに定義しているノード名を指定しています。

描画領域の準備

#contentにグループ要素を示すg要素を作成。

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)
	;

変数linksにノード間の結びつき情報を格納し、同時に描画する際のスタイルも定義しています。

ノード情報を定義

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)
	)
	.on("click", function(d){ alert(d.level + " " + d.name)})
	;

変数circlesにノードの情報を定義。
ちなみにですが、このデモで描画されるノードはパッと見ただけではノードについている名前がわかりません。
そこで各ノードにクリックイベントをつけて、ノード自身のレベルとnameをアラートで出力しています。

call(d3.drag())でノードのドラッグイベントを設定しています。

描画処理

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.forceY(height/2))
	.force('charge', d3.forceManyBody().strength(-10))
	.force("collision", d3.forceCollide(15))
	.velocityDecay(0.5)
	;

Y軸の設定

ここで重要なのは以下の行です。

.force("center", d3.forceY(height/2))

centerはどこを中心にノードが落ちていくかを設定します。ブラックホール的な…?

よく見るForce Graphは、起点になるノードが画面の中心にあり、そこから放射状に広がっていくため、
このcenterにはwidthとheightの1/2を設定して画面の中心を設定することが多いです。

しかし今回の実現したいことは、画面の中心に集まっていくことではありません
ここで行いたい設定は、グラフが画面の高さの中央に表示されるようしたいため、ここではY軸の設定だけを行っています。

X軸の設定

for (var i = 0; i < data_set.length; i++) {
	line_force.nodes()[i].fx = 200 + data_set[i].level * 50;
}

この記事で一番重要なところです。たぶん。

このfor文の中でやっていることは以下です。

  1. とりあえずでsvg要素左端から200pxに起点ノードを配置
  2. ノードのlevel値に従い、X軸をlevel値×50pxずつずらして配置

どのように縦に並ぶかはD3に任せています。

ドラッグイベントへの対応

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;
}

参考にさせていただいたコードはドラッグイベントでノードのX軸とY軸の両方の座標を書き換えていました。
しかし今回はX軸を固定したいので、X軸を変更する箇所をコメントアウトしました。

結果

うまくいきました。

widthとheightの部分だけ変えてCodePenを使ってみました。ナニコレ便利wヤダコレかっこいいw

GitHubにも載せておきました。

 感想

Force Graphを使って少し視点を変えて可視化してみました。
向こうグラフでもある程度は方向性の可視化ができたのかなと思っています。
もちろん有効グラフの方がいいとは思いますが…。

思い付きでこんな可視化をしてみましたが、この可視化方法を有効に使えるオープンデータでも探してみようかなと思いました。
いいオープンデータがあれば教えていただけると幸いです。

参考にさせていただいたサイト様

    1. D3のForce Graphを描画するために参考にさせていただいたサイト様
      d3.jsのフォース(force)を理解するための簡単な例2:各要素を一直線に並ぶ | 古松
    2. nodeの位置を固定するために参考にさせていただいたサイト様
      d3.js Force Simulation の世界 – Qiita
    3. g要素について調査したサイト
      D3.js v4/v5 force simulation ノードに複数のsvg要素を含める方法 – データビジュアライゼーション・ラボ

ありがとうございました!

シェアする

  • このエントリーをはてなブックマークに追加

フォローする