エディタ…JavaScriptのシンタックスハイライトができるもの
Firefox or Chrome … 表示用のブラウザ
Firebug or ChromeのJavaScript Console … JavaScriptのデバッグや、逐次実行が使いやすい
d3.jsではデータの読み込みにHTTPRequestを多用するため、ローカル環境に簡易なWebサーバが構築できると便利.
作業しているhtmlやjsの入っているディレクトリに移動してから、shell (MacOSXのTerminal)から
python -m SimpleHTTPServer
で、port:8000でWebサーバが起動する。
ブラウザからhttp://localhost:8000/ にリクエストすると、ディレクトリ以下のファイルにアクセスできる
はじめにd3.jsを使わずにHTMLを書いてcircleとrectを描画する
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div class="container">
<svg width="600" height="400">
<circle cx="200" cy="100" r="50" fill="red"/> <!-- 座標(200,100)に半径50px 赤の円-->
<rect x="200" y="100" width="100" height="100" fill="blue" style="opacity:0.6"/> <!-- 四角形 透明度60% -->
</svg>
</div>
</body>
</html>
d3特殊な描画環境を提供しているわけではない。Webの標準的な規格であるCSS, HTML5, SVGを操作することでデータを視覚化するためのライブラリである。そのため、例えばcircleやrectなどの描画についてどう書けばよいかは MDN SVG element reference などで調べることができる。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> <!--ライブラリd3.jsを読み込み-->
<title></title>
</head>
<body>
<div class="container">
<svg width="600" height="400">
<circle cx="200" cy="100" r="50" fill="red"/>
<rect x="200" y="100" width="100" height="100" fill="blue" style="opacity:0.6"/>
</svg>
</div>
<script src="firstd3.js" charset="utf-8"></script> <!--これからプログラミングするjsファイルパス-->
</body>
</html>
先ほど描画した図形の属性(サイズ、色)を書き換えたり、新しい図形を追加する
console.log('hello world');
d3.selectAll('circle') // circle要素をすべて取得
.attr('cx', 100) // それらの x座標を100に設定
.attr('cy', 100)
.attr('r', 100)
.attr('fill', 'green')
.style('opacity', 0.5) // 透明度 50%
d3.selectAll('rect') // rect要素をすべて取得
.attr('x', 200)
.attr('y', 100)
.attr('width', 200)
.attr('height', 100)
.attr('fill', '#39c') // 色をRGB値で指定
.style('opacity', 1.0)
// d3で新しくcircle要素を追加
d3.select('svg') // svg要素をひとつ取得
.append('circle') // 取得したsvg要素の中にcircle要素を追加
.attr('cx', 100)
.attr('cy', 200)
.attr('r', 50)
.style('opacity', 0.5)
描画する図形の属性をデータによって決定する
// データをもとにcircle描画する
// <circle>の大きさをdatasetの個々のデータに対応させ, 一列に並べる
var dataset = [10, 70, 80, 40, 20, 50, 30]
var container = d3.select('.container')
var svg = container.append('svg')
.attr('width', 600)
.attr('height', 500)
svg.selectAll("circle")
.data(dataset) // データを渡す
.enter()
.append('circle')
.attr('cx', function(data, i) { return i*80} ) // dataに個々のデータ(10,70,80,40,20,50,30)、iにはデータの番号(0〜6)が渡される
.attr('cy', 100 ) // y座標は全部のcircleで同じ100px
.attr('r', function(data){ return data }) // 半径の大きさは個々データの値にする[10, 70, 80, 40, 20, 50, 30]
.style('opacity', 0.3) //透明度
circleのx座標は0, 80, 160, 240, 320, 400, 480 と i * 80になっている
circleの半径はデータの10, 70, 80, 40, 20, 50, 30になっている
// 2つのデータ(dataset_a, dataset_b)をもとにcircle描画する
// データの種類を区別するためにcircleにクラス名をつける
var dataset_a = [10, 70, 80, 40, 20, 50, 30]
var dataset_b = [30, 90, 100, 60, 40, 70, 50]
var container = d3.select('.container')
var svg = container.append('svg')
.attr('width', 600)
.attr('height', 500)
// dataset_a用
svg.selectAll("circle.data_a") // クラス名 data_a のcircleタグを取得
.data(dataset_a)
.enter()
.append('circle')
.attr('class', 'data_a') // クラス名 data_a を指定
.attr('cx', function(data, i) { return i*80} )
.attr('cy', 100 )
.attr('r', function(data){ return data })
.style('opacity', 0.3) //透明度
// dataset_b用
svg.selectAll("circle.data_b") クラス名 data_b のcircleタグを取得
.data(dataset_b)
.enter()
.append('circle')
.attr('class', 'data_b') // クラス名 data_b を指定
.attr('cx', function(data, i) { return i*80} )
.attr('cy', 200 )
.attr('r', function(data){ return data })
.attr('fill', 'red')
.style('opacity', 0.3)
data.csv
name,size
dog,84
cat,23
fish,5
orange,58
apple,53
var container = d3.select('.container')
d3.csv("http://localhost:8000/data.csv", function(error, dataset) {
console.log(error) // csv受信にエラーがあればlogに
console.log(dataset) // csv受信に成功すれば datasetの中身がlogに
var svg = container.append('svg')
.attr('width',500)
.attr('height',500)
// データを表現する円
svg.selectAll("circle")
.data(dataset)
.enter()
.append('circle')
.attr('cx', 200)
.attr('cy', function(data){ return data.size})
.attr('r', function(data){ return data.size })
.attr('fill', '#39c')
.style('opacity', 0.2)
// dog, cat などの文字ラベルを表示する
svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr('x', 200)
.attr('y', function(data) {return data.size*2})
.attr('fill','#F31')
.text(function(data) {return data.name})
})
data.json
[{"name": "dog", "size": 84},
{"name": " cat", "size": 23},
{"name": " fish", "size": 5},
{"name": " orange", "size": 58},
{"name": " apple", "size": 53}]
上記data.jsonをHTTPリクエストを使って取得
var container = d3.select('.container')
d3.json("http://localhost:8000/data.json", function(error, dataset) {
console.log(error) // json受信にエラーがあればlogに
console.log(dataset) // json受信に成功すれば datasetの中身がlogに
var svg = container.append('svg')
.attr('width',500)
.attr('height',500)
// データを表現する円
svg.selectAll("circle")
.data(dataset)
.enter()
.append('circle')
.attr('cx', 200)
.attr('cy', function(data){ return data.size})
.attr('r', function(data){ return data.size })
.attr('fill', '#39c')
.style('opacity', 0.2)
// dog, cat などの文字ラベルを表示する
svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr('x', 200)
.attr('y', function(data) {return data.size*2})
.attr('fill','#F31')
.text(function(data) {return data.name})
})
取得するjsonやcsvファイルは同じドメインのものでなければならない.
外部のjsonを取りに行こうとするとブラウザのセキュリティ機能によりエラーになる(詳しくはクロスドメインで検索)
例えば
外部ドメインにあるjsonやcsvファイルを扱いたい場合はJSONPを使用する
JSONP
http://ja.wikipedia.org/wiki/JSONP
d3.jsonp
https://github.com/d3/d3-plugins/tree/master/jsonp
1234から8983までの値をとるデータセットを、svgの幅500x高さ300の領域に表示する時
データセットの値をそのまま座標の値にすると、svgの領域からはみ出してしまう。(500<1234)
そのため1234〜8983までのデータを、500x300の領域に収まるように描画するための仕組みが必要
var dataset = [1234, 7000, 8983, 4000, 2120, 5000, 3100]
var container = d3.select('.container')
// datasetの中の1234〜8983までの範囲のデータを50〜450に収まるように変換する関数(scale_x)を作る
var scale_x = d3.scale.linear()
.domain([1234, 8983]) // データのとる範囲を指定する
.range([50, 450]) // 変換したい範囲を指定する
// scale_x(1234) --> 50
// scale_x(2000) --> 89.5405858820493
// scale_x(8983) --> 450
// scale_xを呼び出してみると1234〜8983を50〜450にスケールしているのがわかる
var svg = container.append('svg')
.attr('width', 500)
.attr('height', 300)
.style('background', '#EEE')
svg.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.attr('cx', scale_x) // .attr('cx', function(data) { return scale_x(data)} ) と同じ
.attr('cy', 100)
.attr('r', 10)
.style('opacity', 0.3)
svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr('x', scale_x)
.attr('y', 100)
.attr('fill', '#F31')
.text(function(data){return data})
circleとtextのx座標が50〜449.99999にスケールされ、500pxの中に収まっている
描画した要素をアニメーションさせる
var dataset = [1234, 7000, 8983, 4000, 2120, 5000, 3100]
var container = d3.select('.container')
var width = 500
var height = 300
var padding = 20
var scale_x = d3.scale.linear()
.domain(d3.extent(dataset))
.range([0+padding, width-padding*2])
//色もscale.category10を使って適切に設定
var scale_color = d3.scale.category10(dataset)
// scale_color(1234) --> "#1f77b4"
// scale_color(7000) --> "#ff7f0e"
var svg = container.append('svg')
.attr('width', width)
.attr('height', height)
.style('background', '#EEE')
svg.selectAll('circle')
.data(dataset)
.enter()
.append('circle')
.attr('cx', 0) // 最初はすべてのcircleが x=0
.attr('cy', 100)
.attr('r', 10)
.style('opacity', 0.8)
.transition()
.duration(2000) // 2000ミリ秒かけてアニメーション
.attr('cx', scale_x) // scale_xで計算されたxへ
.attr('fill', scale_color) //scale_colorで計算された色へ
svg.selectAll('text')
.data(dataset)
.enter()
.append('text')
.attr('x', 0) // 最初すべてのtextが x=0
.transition()
.duration(2000) // 2000ミリ秒かけてアニメーション
.attr('x', scale_x) // scale_xで計算されたxへ
.attr('y', 100-padding)
.attr('fill', scale_color) //scale_colorで計算された色へ
.text(function(data){return data})
Scaleのコードを修正 circle,textなどを個別に座標指定するのは大変なのでグループ化する
グループの中の要素はtransformで座標を指定する
var dataset = [1234, 7000, 8983, 4000, 2120, 5000, 3100]
var container = d3.select('.container')
var width = 500
var height = 300
var padding = 20
var scale_x = d3.scale.linear()
.domain(d3.extent(dataset))
.range([0+padding, width-padding*2])
var svg = container.append('svg')
.attr('width', width)
.attr('height', height)
.style('background', '#EEE')
var group = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.attr('transform', // 座標は transformで指定する
function(data) { return 'translate('+scale_x(data)+','+100+')'})
group.append('circle')
.attr('r', 10)
.style('opacity', 0.3)
group.append('text')
.attr('fill', '#F31')
.attr('x', -15) // <g>の座標からの相対的な位置を指定する
.attr('y', 30) // <g>の座標からの相対的な位置を指定する
.text(function(data){return data})
D3.jsをCoffeeScriptで書く利点いくつか
# square = function(x) {
# return x * x;
# };
# square(10)
square = (x) -> x * x
square 10 # 100
# list comprehension
value = (square num for num in [1,2,3,4]) # [1, 4, 9, 16]
# string interpolation
console.log "result is #{square 10} !!" # "result is 100 !!"
group.jsをcoffeescriptで書きなおすと
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<title>1st coffee</title>
</head>
<body>
<div class="container"></div>
<script src="http://coffeescript.org/extras/coffee-script.js" charset="utf-8"></script>
<script type="text/coffeescript" src="group.coffee"></script>
</body>
</html>
dataset = [1234, 7000, 8983, 4000, 2120, 5000, 3100]
container = d3.select('.container')
width = 500
height = 300
padding = 20
scale_x = d3.scale.linear()
.domain(d3.extent dataset) # coffee
#.domain(d3.extent(dataset)) ... js
.range([0+padding, width-padding*2])
svg = container.append('svg')
.attr('width', width)
.attr('height', height)
.style('background', '#EEE')
group = svg.selectAll('g')
.data(dataset)
.enter()
.append('g')
.attr('transform', (data)->"translate(#{scale_x data}, 100)") # coffee
#.attr('transform',
# function(data) { return 'translate('+scale_x(data)+','+100+')'}) ... js
group.append('circle')
.attr('r', 10)
.style('opacity', 0.3)
group.append('text')
.attr('fill', '#F31')
.attr('x', -15)
.attr('y', 30)
.text((data)->data) # coffee
#.text(function(data){return data}) ... js
coffee -c group.coffee # group.jsが出力される
group.js 通常はこのjsをアップして使う
// Generated by CoffeeScript 1.6.3
(function() {
var container, dataset, group, height, padding, scale_x, svg, width;
dataset = [1234, 7000, 8983, 4000, 2120, 5000, 3100];
container = d3.select('.container');
width = 500;
height = 300;
padding = 20;
scale_x = d3.scale.linear().domain(d3.extent(dataset)).range([0 + padding, width - padding * 2]);
svg = container.append('svg').attr('width', width).attr('height', height).style('background', '#EEE');
group = svg.selectAll('g').data(dataset).enter().append('g').attr('transform', function(data) {
return "translate(" + (scale_x(data)) + ", 100)";
});
group.append('circle').attr('r', 10).style('opacity', 0.3);
group.append('text').attr('fill', '#F31').attr('x', -15).attr('y', 30).text(function(data) {
return data;
});
}).call(this);