%load_ext sage
from IPython.core.display import HTML
from string import Template
import json
import nvd3
nvd3.ipynb.initialize_javascript(use_remote=True)
loaded nvd3 IPython extension run nvd3.ipynb.initialize_javascript() to set up the notebook help(nvd3.ipynb.initialize_javascript) for options
%%writefile data/worldcup.csv
"team","region","win","loss","draw","points","gf","ga","cs","yc","rc"
"Netherlands","UEFA",6,0,1,18,12,6,2,23,1
"Spain","UEFA",6,0,1,18,8,2,5,8,0
"Germany","UEFA",5,0,2,15,16,5,3,10,1
"Argentina","CONMEBOL",4,0,1,12,10,6,2,8,0
"Uruguay","CONMEBOL",3,2,2,11,11,8,3,13,2
"Brazil","CONMEBOL",3,1,1,10,9,4,2,9,2
"Ghana","CAF",2,2,1,8,5,4,1,12,0
"Japan","AFC",2,1,1,7,4,2,2,4,0
Overwriting data/worldcup.csv
最初に使用するCSSとHTMLを定義します。
%%writefile css/d3ia.css
text {
font-size: 10px;
}
g > text.active {
font-size: 30px;
}
circle {
fill: pink;
stroke: black;
stroke-width: 1px;
}
circle.active {
fill: red;
}
circle.inactive {
fill: gray;
}
Overwriting css/d3ia.css
%%HTML
<!-- CSSの読み込み -->
<link type="text/css" rel="stylesheet" href="css/d3ia.css" />
<!-- 外部jsファイル -->
<script src="js/colorbrewer.js" type="text/javascript"></script>
<!-- 描画するSVGの定義 -->
<div id='ex1'>
<div id='viz'>
<svg style="width:500px;height:500px;border:1px lightgray solid;" />
</div>
<div id='controls' />
</div>
%%javascript
d3.csv("data/worldcup.csv", function(data) {
overallTeamViz('#ex1', data);
});
function overallTeamViz(topTag, incomingData) {
// 中央に表示用のキャンバス用g(teamG)と出場国用g(overallG)を作成
createFrame(topTag, incomingData);
var teamG = d3.select(topTag).selectAll("g.overallG");
// 出場国のCircleとチーム名を表示
createTeamCircle(teamG);
// ボタンアクションをバインディング
bindButtons(topTag, incomingData);
// mouseアクションをバインディング
bindMouseAction(topTag, teamG);
}
function createFrame(topTag, incomingData) {
d3.select(topTag).select("svg")
.append("g")
.attr("id", "teamsG")
.attr("transform", "translate(50,300)")
.selectAll("g")
.data(incomingData)
.enter()
.append("g")
.attr("class", "overallG")
.attr("transform", function (d,i) {return "translate(" + (i * 50) + ", 0)"});
}
function createTeamCircle(teamG) {
// 各チームのCircleを作成
teamG
.append("circle").attr("r", 0)
.transition()
.delay(function(d,i) {return i * 100})
.duration(500)
.attr("r", 40)
.transition()
.duration(500)
.attr("r", 20);
// チームタイトルを表示
teamG
.append("text")
.style("text-anchor", "middle")
.attr("y", 30)
.style("font-size", "10px")
.text(function(d) {return d.team});
}
function bindButtons(topTag, incomingData) {
// 最初のデータからキーの配列を取り出し、不要なteamとregionを除いてボタンを生成
var dataKeys = d3.keys(incomingData[0])
.filter(function (el) {return el != "team" && el != "region"})
d3.select(topTag).select("#controls").selectAll("button.teams").data(dataKeys).enter().append("button")
.on("click", buttonClick)
.html(function(d) {return d});
// ボタンのアクションをバインディング
function buttonClick(datapoint) {
var maxValue = d3.max(incomingData,
function(d) {return parseFloat(d[datapoint])
});
var radiusScale = d3.scale.linear().domain([0,maxValue]).range([2,20]);
d3.select(topTag).selectAll("g.overallG").select("circle").transition().duration(1000).attr("r",
function(d) {return radiusScale(d[datapoint])})
}
}
function bindMouseAction(topTag, teamG) {
// マウスアクションをバインディング
teamG.on("mouseover", highlightRegion);
teamG.on("mouseout", function() {
d3.select(topTag).selectAll("g.overallG").select("circle").style("fill", "pink")
});
function highlightRegion(d) {
d3.select(topTag).selectAll("g.overallG").select("circle").style("fill", function(p)
{return p.region == d.region ? "red" : "gray"})
}
}
teamGの各g要素にバインディングされたデータは、以下のように data 属性に保持されます。
最初は、ボタンイベントをセットしているbindButtonsをみていきましょう。
dataKeys変数に、キー配列["team","region","win","loss","draw","points","gf","ga","cs","yc","rc"]からteamとregionを除いた配列を取り出します。
%%javascript
var dataKeys = ["team","region","win","loss","draw","points","gf","ga","cs","yc","rc"]
.filter(function (el) {return el != "team" && el != "region"})
element.text(dataKeys);
その後、配列の各要素を名前とするボタンを生成し、onClick時の関数としてbuttonClickをバインドします。
var dataKeys = d3.keys(incomingData[0])
.filter(function (el) {return el != "team" && el != "region"})
d3.select("#controls").selectAll("button.teams").data(dataKeys).enter().append("button")
.on("click", buttonClick)
.html(function(d) {return d});
ボタンクリックのコールバック関数は、datapointで渡されたキーの値から最大値をmaxValueに保持し、各Circleの半径を値の割合に変化させています。
function buttonClick(datapoint) {
var maxValue = d3.max(incomingData,
function(d) {return parseFloat(d[datapoint])
});
var radiusScale = d3.scale.linear().domain([0,maxValue]).range([2,20]);
d3.select(topTag).selectAll("g.overallG").select("circle").transition().duration(1000).attr("r",
function(d) {return radiusScale(d[datapoint])})
}
原因はわからないのですが、%%javascriptで定義した関数はjupyterのセルで共存することができないので、別のセルで定義した関数を再利用することができません。
そこで、Templateを使って各関数を定義することにします。
teamViz = '''
function overallTeamViz(topTag, incomingData) {
createFrame(topTag, incomingData);
var teamG = d3.select(topTag).selectAll("g.overallG");
createTeamCircle(teamG);
bindButtons(topTag, incomingData);
bindMouseAction(topTag, teamG);
}
function createFrame(topTag, incomingData) {
d3.select(topTag).select("svg")
.append("g")
.attr("id", "teamsG")
.attr("transform", "translate(50,300)")
.selectAll("g")
.data(incomingData)
.enter()
.append("g")
.attr("class", "overallG")
.attr("transform", function (d,i) {return "translate(" + (i * 50) + ", 0)"});
}
function createTeamCircle(teamG) {
teamG
.append("circle").attr("r", 0)
.transition()
.delay(function(d,i) {return i * 100})
.duration(500)
.attr("r", 40)
.transition()
.duration(500)
.attr("r", 20);
teamG
.append("text")
.style("text-anchor", "middle")
.attr("y", 30)
.style("font-size", "10px")
.text(function(d) {return d.team});
}
'''
bindButtons = '''
function bindButtons(topTag, incomingData) {
var dataKeys = d3.keys(incomingData[0])
.filter(function (el) {return el != "team" && el != "region"})
d3.select(topTag).select("#controls").selectAll("button.teams").data(dataKeys).enter().append("button")
.on("click", buttonClick)
.html(function(d) {return d});
function buttonClick(datapoint) {
var maxValue = d3.max(incomingData,
function(d) {return parseFloat(d[datapoint])
});
var radiusScale = d3.scale.linear().domain([0,maxValue]).range([2,20]);
d3.select(topTag).selectAll("g.overallG").select("circle").transition().duration(1000).attr("r",
function(d) {return radiusScale(d[datapoint])})
}
}
'''
bindMouseAction = '''
function bindMouseAction(topTag, teamG) {
teamG.on("mouseover", highlightRegion);
teamG.on("mouseout", function() {
d3.select(topTag).selectAll("g.overallG").select("circle").style("fill", "pink")
});
function highlightRegion(d) {
d3.select(topTag).selectAll("g.overallG").select("circle").style("fill", function(p)
{return p.region == d.region ? "red" : "gray"})
}
}
'''
js_text = Template('''
<link type="text/css" rel="stylesheet" href="css/d3ia.css" />
<script src="js/colorbrewer.js" type="text/javascript"></script>
<div id='$example'>
<div id='viz'>
<svg style="width:500px;height:500px;border:1px lightgray solid;" />
</div>
<div id='controls' />
</div>
<script>
d3.csv("data/worldcup.csv", function(data) {
overallTeamViz('#$example', data);
});
$teamViz
$bindButtons
$bindMouseAction
</script>
''')
example='ex2'
html_text = js_text.substitute({'example': example, 'teamViz': teamViz,
'bindButtons': bindButtons, 'bindMouseAction': bindMouseAction, 'extention': ""})
HTML(html_text)
バイディングされたデータで見たとおりバインディングされたデータや可視化された要素全てが、DOMの中に保持されています。
javascriptでこれらのDOM要素にアクセスする方法を見てみましょう。
%%javascript
d3.select('#ex2').select("circle").each(function(d,i) {
console.log(d);console.log(i);console.log(this);
});
デベロッパーツールのConsoleには、以下のように表示されます。
thisは、選択された要素のDOMオブジェクトを参照していることがわかりました。
また、node()メソッドで選択された要素を取り出すこともできます。
%%javascript
console.log(d3.select('#ex2').select("circle").node());
ボタンをクリックした時に、値によってCircleの半径だけでなく、色も変える例が紹介されています。
最初にLAB ramp関数を使って数値で色を黄色から青色の間を線形補間して表示する方法を示します。
var ybRamp = d3.scale.linear()
.interpolate(d3.interpolateLab)
.domain([0,maxValue]).range(["yellow", "blue"]);
bindButtons = '''
function bindButtons(topTag, incomingData) {
var dataKeys = d3.keys(incomingData[0])
.filter(function (el) {return el != "team" && el != "region"})
d3.select(topTag).select("#controls").selectAll("button.teams").data(dataKeys).enter().append("button")
.on("click", buttonClick)
.html(function(d) {return d});
function buttonClick(datapoint) {
var maxValue = d3.max(incomingData, function(el) {
return parseFloat(el[datapoint ]);
});
var ybRamp = d3.scale.linear()
.interpolate(d3.interpolateLab)
.domain([0,maxValue]).range(["yellow", "blue"]);
var radiusScale = d3.scale.linear().domain([0,maxValue]).range([2,20]);
d3.selectAll("g.overallG").select("circle").transition().duration(1000)
.style("fill", function(p) {
return ybRamp(p[datapoint ])}
) .attr("r", function(p) {
return radiusScale(p[datapoint ])
});
}
}
'''
example='ex3'
html_text = js_text.substitute({'example': example, 'teamViz': teamViz,
'bindButtons': bindButtons, 'bindMouseAction': bindMouseAction, 'extention': ""})
HTML(html_text)
次にcolorbrewerライブラリを使って、赤系統の3色でグルーピングする例を示します。
var colorQuantize = d3.scale.quantize()
.domain([0,maxValue]).range(colorbrewer.Reds[3]);
bindButtons = '''
function bindButtons(topTag, incomingData) {
var dataKeys = d3.keys(incomingData[0])
.filter(function (el) {return el != "team" && el != "region"})
d3.select(topTag).select("#controls").selectAll("button.teams").data(dataKeys).enter().append("button")
.on("click", buttonClick)
.html(function(d) {return d});
function buttonClick(datapoint) {
var maxValue = d3.max(incomingData, function(el) {
return parseFloat(el[datapoint ]);
});
var colorQuantize = d3.scale.quantize()
.domain([0,maxValue]).range(colorbrewer.Reds[3]);
var radiusScale = d3.scale.linear().domain([0,maxValue]).range([2,20]);
d3.selectAll("g.overallG").select("circle").transition().duration(1000)
.style("fill", function(p) {
return colorQuantize(p[datapoint ])}
) .attr("r", function(p) {
return radiusScale(p[datapoint ])
});
}
}
'''
example='ex4'
html_text = js_text.substitute({'example': example, 'teamViz': teamViz,
'bindButtons': bindButtons, 'bindMouseAction': bindMouseAction, 'extention': ""})
HTML(html_text)
次に画像を図形に表示する例を試してみます。
画像の表示には、"xlink:ref"を使用します。以下の例では、imageタグをtextの前に挿入し、 属性hrefに画像のURIをセットしています。
%%javascript
d3.select('#ex4').selectAll("g.overallG").insert("image", "text")
.attr("xlink:href", function(d) {
return "images/" + d.team + ".png";
})
.attr("width", "45px").attr("height", "20px").attr("x", "-22")
.attr("y", "-10");
%%writefile -a css/d3ia.css
#modal {
position:fixed;
left:150px;
top:100px;
z-index:1;
background: white;
border: 1px black solid;
box-shadow: 10px 10px 5px #888888;
}
tr {
border: 1px gray solid;
}
td {
font-size: 10px;
}
td.data {
font-weight: 900;
}
Appending to css/d3ia.css
表示するダイアログのHTMLは、以下のようになります。
%%writefile resources/modal.html
<table> <tr>
<th>Statistics</th>
</tr>
<tr><td>Team Name</td><td class="data"></td></tr>
<tr><td>Region</td><td class="data"></td></tr>
<tr><td>Wins</td><td class="data"></td></tr>
<tr><td>Losses</td><td class="data"></td></tr>
<tr><td>Draws</td><td class="data"></td></tr>
<tr><td>Points</td><td class="data"></td></tr>
<tr><td>Goals For</td><td class="data"></td></tr>
<tr><td>Goals Against</td><td class="data"></td></tr>
<tr><td>Clean Sheets</td><td class="data"></td></tr>
<tr><td>Yellow Cards</td><td class="data"></td></tr>
<tr><td>Red Cards</td><td class="data"></td></tr>
</table>
Overwriting resources/modal.html
example='ex5'
html_text = js_text.substitute({'example': example, 'teamViz': teamViz,
'bindButtons': bindButtons, 'bindMouseAction': bindMouseAction,
'extention': ""})
HTML(html_text)
%%javascript
var teamG = d3.select('#ex5').selectAll("g.overallG");
teamG.on("click", teamClick);
d3.text("resources/modal.html", function(data) {
d3.select('#ex5').append("div").attr("id", "modal").html(data);
});
function teamClick(d) {
d3.select('#ex5').selectAll("td.data").data(d3.values(d)).html(function(p) {return p});
}