D3.js レイアウト – diagonalで曲線をつなぐ

このページは"D3.jsチュートリアル"シリーズの12 / 20です。

ツリーチャートなど曲線で点同士をつなぐビジュアライゼーションはたくさんあります。d3.svg.diagonal()を使うと、ある地点とある地点の間に曲線を描くための、path要素のデータを作ることができます。これをpath要素に入れると、曲線を描いてくれます。

diagonalの設定は基本的には下のようにするだけでOKです。

var diagonal = d3.svg.diagonal();

ここで若干複雑なのは、使うデータ構造です。diagonalを作るために必要データは、曲線の開始点のx,yと終了点のx,yです。それを表現するためには、下のように開始点を表すsourceというキーと終了点を表すtargetというキーを含んだ辞書が必要です。それぞれの中にはxとyの値が入っています。これが一本の曲線のデータになります。

var data = {source: {x:20, y:10}, target: {x:200, y:500}};

データのキー名がsourceやtargetとは違う時、.source()や.target()を使って、sourceやtargetに使う名前を変えることもできます。たとえば下のようにstartとendという名前に変えることができます。

var diagonal = d3.svg.diagonal()
.source(function(d) { return d.start; }) // function(d){}を使って、sourceに使うキーを指定。
.target(function(d) { return d.end; }); // function(d){}を使って、targetに使うキーを指定。

実際に曲線を作る時は、下のようにpath要素のd属性に入れます。

svg.selectAll("path").data(data).enter()
.append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d",diagonal); // diagonalで作ったpathデータをdに入れている。

では実際の例を見てみます。

 

二本のdiagonal曲線を描く

結果:

d3レイアウト-diagonal1
クリックすると実際のページが開きます。

コード:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
</head>
<body>

	<script src="http://d3js.org/d3.v3.min.js"></script>

	<script>
	var width = 760,
	height = 600;

	var svg = d3.select("body").append("svg")
	.attr("width", width)
	.attr("height", height);

	// 2つの曲線データをリストに入れている。
	var data = [
	{source: {x:20, y:10}, target: {x:200, y:300}},
	{source: {x:40, y:10}, target: {x:260, y:300}}
	];

	// diagonalのpathジェネレータ設定。
	var diagonal = d3.svg.diagonal();

	// path要素で曲線を描く。
	svg.selectAll("path").data(data).enter()
	.append("path")
	.attr("fill", "none")
	.attr("stroke", "black")
	.attr("d",diagonal); // diagonalがデータを取って、曲線のpathデータに変換している。
	</script>

</body>
</html>

解説:
この例では2本の線を引いています。開始点と終了点の間の線がグネっと曲がっていることが分かります。

2本の線を引くために、下のようにリストデータを使っています。

// 2つの曲線データをリストに入れている。
var data = [
{source: {x:20, y:10}, target: {x:200, y:300}},
{source: {x:40, y:10}, target: {x:260, y:300}}
];

diagonalの設定は一番基本的な形にしています。

// diagonalのpathジェネレータ設定。
var diagonal = d3.svg.diagonal();

作ったdiagonalのpathジェネレータをpathのdに入れることで、pathにバインドされたデータから自動的にsourceとtargetというキーを探して、それぞれのxとyから曲線のpathデータを作りってくれます。d属性にそのpathデータが入るので、path曲線を描いてくれます。

// path要素で曲線を描く。
svg.selectAll("path").data(data).enter()
.append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d",diagonal); // diagonalがデータを取って、曲線のpathデータに変換している。

 

半径と角度を使ってdiagonal曲線を描く

Chord diagramのように、円環の中で点通しを曲線で結ぶビジュアライゼーションもたくさんあります。そういった曲線を作るためには、xとyという座標ではなく、半径と角度という円状の座標を使って、開始点と終了点を決める必要があります。

d3.svg.diagonal.projection()の中に関数を入れると、xやyをここで変換した後にdiagonalのデータを作るようになります。
つまりここでxとyを変換して、x=角度、y=半径という形にすれば良いわけです。
具体的には下のように、xとyを角度と半径に変換する関数radialProjectionを作って、.projection(radialProjection)という風に入れます。

var diagonal = d3.svg.diagonal()
.projection(radialProjection) // radialプロジェクションを使っている。

// xを角度、yを半径として解釈するプロジェクション関数。
function radialProjection(d) {
  var r = d.y, a = (d.x - 90) / 180 * Math.PI;
  return [r * Math.cos(a), r * Math.sin(a)];
}

さらにデータも、xを角度、yは半径というデータに変えます。たとえば下のデータでは、xは0~360度の範囲に収めています。

var data = [{"source":{"x":0,"y":10},"target":{"x":135,"y":100}},{"source":{"x":45,"y":10},"target":{"x":299,"y":100}},{"source":{"x":90,"y":10},"target":{"x":348,"y":100}},{"source":{"x":135,"y":10},"target":{"x":71,"y":100}},{"source":{"x":180,"y":10},"target":{"x":32,"y":100}},{"source":{"x":225,"y":10},"target":{"x":246,"y":100}},{"source":{"x":270,"y":10},"target":{"x":205,"y":100}},{"source":{"x":315,"y":10},"target":{"x":-30,"y":100}}];

あとは1と同じようにpathにデータを読み込んで、dにdiagonalのpathジェネレータを入れればOKです。
では具体的な例です。

結果:

d3レイアウト-diagonal2_radial
クリックすると実際のページが開きます。

コード:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
</head>
<body>

	<script src="http://d3js.org/d3.v3.min.js"></script>

	<script>
	var width = 960,
	height = 700;

	var svg = d3.select("body").append("svg")
	.attr("width", width)
	.attr("height", height)
	.attr("transform","translate(200,200)");

	// sourceは円の内側で、半径yは常に10、角度xはインデックスで一定に360度を回る。
	// targetは円の外側で、半径yは常に100、角度xは、ランダムにした。
	var data = [{"source":{"x":0,"y":10},"target":{"x":135,"y":100}},{"source":{"x":45,"y":10},"target":{"x":299,"y":100}},{"source":{"x":90,"y":10},"target":{"x":348,"y":100}},{"source":{"x":135,"y":10},"target":{"x":71,"y":100}},{"source":{"x":180,"y":10},"target":{"x":32,"y":100}},{"source":{"x":225,"y":10},"target":{"x":246,"y":100}},{"source":{"x":270,"y":10},"target":{"x":205,"y":100}},{"source":{"x":315,"y":10},"target":{"x":-30,"y":100}}];

	svg.append("circle")
	.attr({cx:0,cy:0,fill:"red",r:5});

	var diagonal = d3.svg.diagonal()
	.projection(radialProjection) // radialプロジェクションを使っている。

	// xを角度、yを半径として解釈するプロジェクション。
	function radialProjection(d) {
	  var r = d.y, a = (d.x - 90) / 180 * Math.PI;
	  return [r * Math.cos(a), r * Math.sin(a)];
	}

	svg.selectAll("path").data(data).enter()
	.append("path")
	.attr("fill", "none")
	.attr("stroke", "black")
	.attr("d",diagonal);

	// 外側のランダムな角度 = targetのxに合わせて回転する円。
	svg.selectAll(".circles").data(data).enter()
	.append("circle")
	.attr("class","circles")
	.attr("r",5)
	.attr("fill","skyBlue")
	.attr("cx",0)
	.attr("cy",-100)
	.attr("transform",function(d){ return "rotate(" + d.target.x + ")"; });

	</script>

</body>
</html>

解説:
できた画像を見ると、中心から円状の点をぐにゃっと結ぶ線ができました。何かの触手のように見えます。

この肝はコードというよりデータです。
データの開始点sourceと終了点targetの半径xは、どちらも一定の値にしています。だから開始点は常に円の中心で、終了点は常に円周にあります。
データの角度は、開始点は一定の角度で360度をまわり、終了点はランダムにしています。ランダムなのでところどころ絡まったような線もできます。

var data = [{"source":{"x":0,"y":10},"target":{"x":135,"y":100}},{"source":{"x":45,"y":10},"target":{"x":299,"y":100}},{"source":{"x":90,"y":10},"target":{"x":348,"y":100}},{"source":{"x":135,"y":10},"target":{"x":71,"y":100}},{"source":{"x":180,"y":10},"target":{"x":32,"y":100}},{"source":{"x":225,"y":10},"target":{"x":246,"y":100}},{"source":{"x":270,"y":10},"target":{"x":205,"y":100}},{"source":{"x":315,"y":10},"target":{"x":-30,"y":100}}];

さらにデータの開始点と終了点のデータをcircleに流用して、線の端の位置に小さい円を付けてみました。

// 外側のランダムな角度 = targetのxに合わせて回転する円。
svg.selectAll(".circles").data(data).enter()
.append("circle")
.attr("class","circles")
.attr("r",5)
.attr("fill","skyBlue")
.attr("cx",0)
.attr("cy",-100)
.attr("transform",function(d){ return "rotate(" + d.target.x + ")"; });

diagonalは、他のレイアウトと組み合わせて使うととても見た目がおもしろいビジュアライゼーションになります。


One thought on “D3.js レイアウト – diagonalで曲線をつなぐ

  1. 一本の曲線のデータを指定するとき
    (「それぞれの中にはxとyの値が入っています。これが一本の曲線のデータになります。」の部分)
    var data = {source: {x:20, y:10}, target: {x:200, y:500}};

    var data = [var data = {source: {x:20, y:10}, target: {x:200, y:500}};]

    と[ ] で囲みませんか?

コメントを残す