d3.jsな日々

d3.jsでデータ可視化する際の覚書です

描画の基本:selectAll-data-enter-append

d3.jsのサンプルコードを見ると、selectAll(), data(), enter(), append()というシーケンスが多用されている。これらの関係や挙動が、初心者にはわかりにくい。そこで、実際の挙動を見ながら、理解してみる。
参考:knowledge stockpile: Understanding selectAll, data, enter, append sequence in D3.js

【例1】 下のスクリプトをブラウザで表示すると…

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>D3: Creating paragraphs dynamically from data</title>
		<script type="text/javascript" src="../d3/d3.v3.js"></script>
	</head>
	<body>
		<script type="text/javascript">
			var dataset = [ 5, 10, 15, 20, 25 ];
			d3.select("body").selectAll("p")
				.data(dataset)
				.enter()
				.append("p")
				.text(function(d){return "data is "+d;});
		</script>
	</body>
</html>

...次のようになる。
f:id:yasuda0404:20130416090559p:plain:w120


【例2】あらかじめ'p'タグ要素が存在する場合を見る。下の例では、pタグブロックを2つ作った。

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>D3: Creating paragraphs dynamically from data</title>
		<script type="text/javascript" src="../d3/d3.v3.js"></script>
	</head>
	<body>
    	<div id="ex1">
        	<p>line 1</p>
            <p>line 2</p>
        </div>
		<script type="text/javascript">
			var dataset = [ 5, 10, 15, 20, 25 ];
			d3.select("body").selectAll("p")
				.data(dataset)
				.enter()
				.append("p")
				.text(function(d){return "data is "+d;});
		</script>
	</body>
</html>

この表示結果は次の通り。既存のpタグブロックの数に相当するデータは無視される。
f:id:yasuda0404:20130416120041p:plain:w120

この挙動は、次のように理解される。


  1. d3.select("body").selectAll("p")は、bodyタグ内のすべてのp要素を取り出す。取り出したp要素には、デフォルトキー、0,1,2,...がアサインされる。

  2. .data(dataset)は、データセット(dataset)の各要素を順に取り出す「ループ」になるとともに、キーをアサインする。デフォルトのキーはやはり、0,1,2,3,... となる。

  3. .enter()以下は、data「ループ」の現在のキーに相当する要素が存在ない場合のみ、実行される。


つまり、
【例1】では、既存p要素は存在しないため、datasetのすべての要素に対して.enter()以下が実行される
【例2】では、2つのp要素が存在し、これらのキーは0と1である。このため、datasetのキー0,1、すなわち最初の2つの要素に対して.enter()以下は実行されない。キー2,3,4の要素のみ.enter()以下が実行される
と言うことになる。

.dataset()のキーを明示的に変更するには、第2引数で指定する。

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>D3: Creating paragraphs dynamically from data</title>
		<script type="text/javascript" src="../d3/d3.v3.js"></script>
	</head>
	<body>
    	<div id="ex1">
        	<p>line 1</p>
            <p>line 2</p>
        </div>
		<script type="text/javascript">
			var dataset = [ 5, 10, 15, 20, 25 ];
			d3.select("body").selectAll("p")
				.data(dataset,function(d){return d+2;})
				.enter()
				.append("p")
				.text(function(d){return "data is "+d;});
		</script>
	</body>
</html>

上のスクリプトの表示結果は次の通り。pタグ要素が存在していても、'd+2'によって.data(dataset)「ループ」のキーは、2,3,4,... となり、既存のpタグ要素のキー(0,1)と重複しないため、全てdataset要素に対してenter()以下が実行されることになる。
f:id:yasuda0404:20130416131009p:plain:w120


【例3】ちなみに、.selectAll("p")を外すとどうなるか実験してみた。

<body>
		<script type="text/javascript">
			var dataset = [ 5, 10, 15, 20, 25 ];
			d3.select("body")
				.data(dataset)
				.enter()
				.append("p")
				.text(function(d){return "data is "+d;});
		</script>

表示結果は次の通り、データ配列の一番目の要素が表示されない。
f:id:yasuda0404:20130416090643p:plain:w120
これは、.select("body")のキー、0が有効になっていると思われる。このため、.data(dataset)の最初の要素に対しては.enter()以下が実行されないのだろう。