読者です 読者をやめる 読者になる 読者になる

プログラミングの実験場

Haskell/Webアプリ/画像処理/可視化/ITによる生産性向上 など

D3.jsを使ってブラウザ上で宣言的なデータ可視化

D3.jsというJavaScriptでのDOM操作・可視化ライブラリ http://d3js.org/ が面白そう。D3.jsはHTMLのDOM要素とSVGに対して宣言的にプログラミングすることで可視化が見通しよく書けるライブラリ。D3.jsで生成されるのは図形を含めてすべてDOM要素であるので、CSSや他のJSコードと自在に組み合わせられるという利点がある。便利関数がたくさんあって多機能な感じ。jQueryのような感覚でDOM要素の集合を選択し、それに対してデータ(配列、ハッシュの配列、配列の配列)を割り当てて、DOM要素の属性を変更できる。

資料

最小構成

<html>
<head>
	<title>D3.js test</title>
</head>
<body>
	<div id='div1'></div>
	<div id='div2'></div>

	<!-- 必要なJSはこのファイル1つだけ。JQueryなども不要。 -->
	<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
	<!-- 自分のコード -->
	<script src="d3test.js" charset="utf-8"></script>

</body>
</html>

DOM操作のライブラリなので、JSファイル1つのみで、専用のCSSなどは無い。

基本的なDOM操作

jQueryのように、DOM要素を選択し、メソッドチェーンで子要素を追加したり、属性や内容を書き換えられる。

function test0(){
	d3.select('body').append('div').attr('id','graph').text('Hey');
}

SVG要素を操作する例。

function test1(){
	//SVG要素を作成。
	var svg1 = d3.select("#div1").append("svg")
		.attr("width",300)
		.attr("height",300)
		.style("background","#eef");

	//子要素を追加。
	svg1.append('circle')
		.attr("cx", 100)
	    .attr("cy", function() {return 10+Math.random()*200;}) //値の指定に、関数で値を返すこともできる。
	    .attr("r", 20)
	    .style('fill',function(){return 'pink';})
		.on("mouseover", function(){d3.select(this).style("fill", "gray");})    //イベントハンドラの指定。(この動作ならCSSで書いたほうが良さそうだが。)
	    .on("mouseout", function(){d3.select(this).style("fill", "pink");});
}

2次元配列からデータをとってきて、テーブルのセルを生成する例

//参考:http://christopheviau.com/d3_tutorial/
function test2(){
    var array2d = [[1,3,2],[4,9,6],[1,2,4]];

    // selectAllでデータのプレースホルダを作り、それにdataでデータを追加する。そしてenterでデータに対応する集合が実際に出来る。
    // 解説はこれが分かりやすい。http://bost.ocks.org/mike/join/

    d3.select("#div2 table")
		.style("background","#fee")

	    .selectAll("tr")   //selectAll,data.enterという一組でデータを追加。
	    .data(array2d)
	    .enter().append("tr")	//データarray2dの各要素(array2d[0],array2d[1],array2d[2])に対応して行を作成。
	    
	    .selectAll("td")	//各行に対して、列の作成
	    .data(function(d){	
	    	console.log(d);
	    	return d;
	    })
	    .enter().append("td")

	    .style("background",function(d,i,j){ //dは2次元配列の中身、(i,j)は2次元配列のインデックス。
	    	var d_max = 9;
	    	var d_min = 0;
	    	console.log(i,j);
	    	//HSLカラーでヒートマップを表現。
	    	//色の指定については、https://github.com/mbostock/d3/wiki/Colors
	       	return d3.hsl(240*(1-d/d_max),1,0.5);
	    })
	    .attr("width",50)	//各tdのサイズを指定。
	    .attr("height",50)

	    .text(function(d,i,j){ return "("+i+","+j+"): "+d; });
}

一番最後の例など、メソッドチェーンが長大。このような入れ子の構造でメソッドの返り値が何の「型」なのか、そして選択されたDOMの集合がどう変わるのか、というのがまだちょっと理解できていない。
こういうところこそ、Haskellのような型が便利になる場面なのだと思う。Haskellのライブラリは、型があるので理解が早い。と、無理やりHaskellにつなげた所で今日のところはここで終わり。