#!/usr/bin/env python # coding: utf-8 # # Chapter3 # D3.js in Actionの3章の勉強ノートです。 # # KernelはPython2を使用します。 # In[1]: get_ipython().run_line_magic('load_ext', 'sage') from IPython.core.display import HTML from string import Template import json import nvd3 nvd3.ipynb.initialize_javascript(use_remote=True) # ## データの可視化 # 3章では、svgの図形の他にHTMLページや画像を表示する方法を説明しています。 # # ### W杯のデータ # 例題に使用しているのは、以下のようなW杯のデータです。 # In[2]: get_ipython().run_cell_magic('writefile', 'data/worldcup.csv', '"team","region","win","loss","draw","points","gf","ga","cs","yc","rc"\n"Netherlands","UEFA",6,0,1,18,12,6,2,23,1\n"Spain","UEFA",6,0,1,18,8,2,5,8,0\n"Germany","UEFA",5,0,2,15,16,5,3,10,1\n"Argentina","CONMEBOL",4,0,1,12,10,6,2,8,0\n"Uruguay","CONMEBOL",3,2,2,11,11,8,3,13,2\n"Brazil","CONMEBOL",3,1,1,10,9,4,2,9,2\n"Ghana","CAF",2,2,1,8,5,4,1,12,0\n"Japan","AFC",2,1,1,7,4,2,2,4,0\n') # ### CSSの定義 # 最初に使用するCSSとHTMLを定義します。 # In[3]: get_ipython().run_cell_magic('writefile', 'css/d3ia.css', 'text {\n font-size: 10px;\n}\ng > text.active {\n font-size: 30px;\n}\ncircle {\n fill: pink;\n stroke: black;\n stroke-width: 1px;\n}\ncircle.active {\n fill: red;\n}\ncircle.inactive {\n fill: gray;\n}\n') # In[4]: get_ipython().run_cell_magic('HTML', '', '\n\n\n\n\n\n
\n
\n \n
\n
\n
\n') # In[5]: get_ipython().run_cell_magic('javascript', '', 'd3.csv("data/worldcup.csv", function(data) {\n overallTeamViz(\'#ex1\', data);\n});\n\nfunction overallTeamViz(topTag, incomingData) {\n // 中央に表示用のキャンバス用g(teamG)と出場国用g(overallG)を作成\n createFrame(topTag, incomingData);\n \n var teamG = d3.select(topTag).selectAll("g.overallG");\n // 出場国のCircleとチーム名を表示\n createTeamCircle(teamG);\n // ボタンアクションをバインディング\n bindButtons(topTag, incomingData);\n // mouseアクションをバインディング\n bindMouseAction(topTag, teamG);\n}\n\n\nfunction createFrame(topTag, incomingData) {\n d3.select(topTag).select("svg")\n .append("g")\n .attr("id", "teamsG")\n .attr("transform", "translate(50,300)")\n .selectAll("g")\n .data(incomingData)\n .enter()\n .append("g")\n .attr("class", "overallG")\n .attr("transform", function (d,i) {return "translate(" + (i * 50) + ", 0)"});\n}\n\nfunction createTeamCircle(teamG) {\n // 各チームのCircleを作成\n teamG\n .append("circle").attr("r", 0)\n .transition()\n .delay(function(d,i) {return i * 100})\n .duration(500)\n .attr("r", 40)\n .transition()\n .duration(500)\n .attr("r", 20); \n // チームタイトルを表示\n teamG\n .append("text")\n .style("text-anchor", "middle")\n .attr("y", 30)\n .style("font-size", "10px")\n .text(function(d) {return d.team});\n}\n\nfunction bindButtons(topTag, incomingData) {\n // 最初のデータからキーの配列を取り出し、不要なteamとregionを除いてボタンを生成\n var dataKeys = d3.keys(incomingData[0])\n .filter(function (el) {return el != "team" && el != "region"}) \n d3.select(topTag).select("#controls").selectAll("button.teams").data(dataKeys).enter().append("button")\n .on("click", buttonClick)\n .html(function(d) {return d}); \n \n // ボタンのアクションをバインディング\n function buttonClick(datapoint) {\n var maxValue = d3.max(incomingData, \n function(d) {return parseFloat(d[datapoint])\n });\n var radiusScale = d3.scale.linear().domain([0,maxValue]).range([2,20]);\n d3.select(topTag).selectAll("g.overallG").select("circle").transition().duration(1000).attr("r", \n function(d) {return radiusScale(d[datapoint])})\n } \n}\n\nfunction bindMouseAction(topTag, teamG) {\n // マウスアクションをバインディング\n teamG.on("mouseover", highlightRegion);\n teamG.on("mouseout", function() {\n d3.select(topTag).selectAll("g.overallG").select("circle").style("fill", "pink")\n });\n\n function highlightRegion(d) {\n d3.select(topTag).selectAll("g.overallG").select("circle").style("fill", function(p) \n {return p.region == d.region ? "red" : "gray"})\n } \n}\n') # ## コードのメモ # [図3.3](http://bl.ocks.org/emeeks/8ec13f7ffbbf1d3c4bd8) # を機能ごとに分けてみました。 # この小さなコードで、すごい表現ができるものだと感心しました。 # # 3章の説明を簡単にまとめてみます。 # # - g要素:SVGの要素をグルーピングするタグ、g要素単位でアニメーションや座標変換を行います # - 描画用のキャンバスフレームをセット(createFrame) # - 各チームのCircleとネームを表示(createTeamCircle) # - ボタンのアクションをバインディング(bindButtons) # - マウスアクションをバインディング(bindMouseAction) # # ### バイディングされたデータ # teamGの各g要素にバインディングされたデータは、以下のように __data__ 属性に保持されます。 # # ![バインディングされたデータ](images/data_binding.png) # ### イベント処理 # # 最初は、ボタンイベントをセットしているbindButtonsをみていきましょう。 # # dataKeys変数に、キー配列["team","region","win","loss","draw","points","gf","ga","cs","yc","rc"]からteamとregionを除いた配列を取り出します。 # # In[6]: get_ipython().run_cell_magic('javascript', '', 'var dataKeys = ["team","region","win","loss","draw","points","gf","ga","cs","yc","rc"]\n .filter(function (el) {return el != "team" && el != "region"}) \nelement.text(dataKeys);\n') # その後、配列の各要素を名前とするボタンを生成し、onClick時の関数としてbuttonClickをバインドします。 # ```javascript # 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の半径を値の割合に変化させています。 # # ```javascript # 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を使って各関数を定義することにします。 # In[7]: 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('''
''') example='ex2' html_text = js_text.substitute({'example': example, 'teamViz': teamViz, 'bindButtons': bindButtons, 'bindMouseAction': bindMouseAction, 'extention': ""}) # In[8]: HTML(html_text) # ## DOMの操作 # バイディングされたデータで見たとおりバインディングされたデータや可視化された要素全てが、DOMの中に保持されています。 # # javascriptでこれらのDOM要素にアクセスする方法を見てみましょう。 # In[9]: get_ipython().run_cell_magic('javascript', '', 'd3.select(\'#ex2\').select("circle").each(function(d,i) {\n console.log(d);console.log(i);console.log(this);\n});\n') # デベロッパーツールのConsoleには、以下のように表示されます。 # # ![DOM_this](images/dom_this.png) # thisは、選択された要素のDOMオブジェクトを参照していることがわかりました。 # # また、node()メソッドで選択された要素を取り出すこともできます。 # # In[10]: get_ipython().run_cell_magic('javascript', '', 'console.log(d3.select(\'#ex2\').select("circle").node());\n') # ## カラーマッピング # ボタンをクリックした時に、値によってCircleの半径だけでなく、色も変える例が紹介されています。 # # 最初にLAB ramp関数を使って数値で色を黄色から青色の間を線形補間して表示する方法を示します。 # # ```javascript # var ybRamp = d3.scale.linear() # .interpolate(d3.interpolateLab) # .domain([0,maxValue]).range(["yellow", "blue"]); # ``` # In[11]: 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 ]) }); } } ''' # In[12]: example='ex3' html_text = js_text.substitute({'example': example, 'teamViz': teamViz, 'bindButtons': bindButtons, 'bindMouseAction': bindMouseAction, 'extention': ""}) HTML(html_text) # 次にcolorbrewerライブラリを使って、赤系統の3色でグルーピングする例を示します。 # # ```javascript # var colorQuantize = d3.scale.quantize() # .domain([0,maxValue]).range(colorbrewer.Reds[3]); # ``` # In[13]: 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 ]) }); } } ''' # In[14]: 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をセットしています。 # # ![xlink_image](images/xlink_image.png) # # In[15]: get_ipython().run_cell_magic('javascript', '', 'd3.select(\'#ex4\').selectAll("g.overallG").insert("image", "text")\n .attr("xlink:href", function(d) {\n return "images/" + d.team + ".png";\n })\n .attr("width", "45px").attr("height", "20px").attr("x", "-22")\n .attr("y", "-10");\n') # ### HTMLを表示する # 最後に画像にHTMLを表示する例を試してみます。このほかにも3章にはとても興味深い例題が紹介されています。 # # ダイアログとして表示するために、以下のCSSを追加します。 # In[16]: get_ipython().run_cell_magic('writefile', '-a css/d3ia.css', '#modal {\n position:fixed;\n left:150px;\n top:100px;\n z-index:1;\n background: white;\n border: 1px black solid;\n box-shadow: 10px 10px 5px #888888;\n}\n tr {\n border: 1px gray solid;\n}\n td {\n font-size: 10px;\n}\ntd.data {\n font-weight: 900;\n }\n') # 表示するダイアログのHTMLは、以下のようになります。 # In[17]: get_ipython().run_cell_magic('writefile', 'resources/modal.html', '\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Statistics
Team Name
Region
Wins
Losses
Draws
Points
Goals For
Goals Against
Clean Sheets
Yellow Cards
Red Cards
\n') # In[18]: example='ex5' html_text = js_text.substitute({'example': example, 'teamViz': teamViz, 'bindButtons': bindButtons, 'bindMouseAction': bindMouseAction, 'extention': ""}) HTML(html_text) # In[19]: get_ipython().run_cell_magic('javascript', '', 'var teamG = d3.select(\'#ex5\').selectAll("g.overallG");\nteamG.on("click", teamClick);\n\nd3.text("resources/modal.html", function(data) {\n d3.select(\'#ex5\').append("div").attr("id", "modal").html(data);\n});\n\nfunction teamClick(d) {\n d3.select(\'#ex5\').selectAll("td.data").data(d3.values(d)).html(function(p) {return p});\n} \n') # In[ ]: