#!/usr/bin/env python
# coding: utf-8
# # Chapter2
# D3.js in Actionの2章の勉強ノートです。
#
# 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)
# ## データの読み込み
# D3では様々なデータをサポートしています。
# - TEXT: d3.text()
# - XML: d3.xml()
# - CSV: d3.csv()
# - JSON: d3.json()
# - HTML: d3.html()
#
# pythonとのインタフェースを取ることを考えると、一般的に構造を保持できるJSONとCSVがデータの受け渡しに使われるます。
#
# 例として以下のようなcities.csvを読み込んでみましょう。
# In[2]:
get_ipython().system('cat data/cities.csv')
# 読み込まれたデータは、function(error, data)形式のコールバックで与えられます。
# このコールバックの中で実行したい処理を記述する方式になります。
# In[3]:
get_ipython().run_cell_magic('javascript', '', 'd3.csv("data/cities.csv",function(error,data) {console.log(error,data)});\n')
# javascriptのコンソールに以下のようにデータの内容が出力されます。
#
# ![cities console log](images/cities_log.png)
#
# これを見るとCSVのデータがヘッダのカラム名をキーとする辞書の配列として渡されていることがわかります。
# ## Pythonデータの受け渡し
# jupyterの計算結果をD3に渡す方法を以下に紹介します。
#
# jsonとTemplateライブラリを使用します。
# In[5]:
from string import Template
import json
import pandas as pd
# pandasを使ってcities.csvを読み込み、データフレームdfにセットします。
# In[6]:
df = pd.read_csv("data/cities.csv")
df.head()
# Templateを使ってscriptタグにdfを変数dataに代入します。
#
# $python_dataの置換で、json.dumpsとto_dict(orient='records')を使用するのがポイントです。
# In[7]:
data_text = Template('''
''')
data_text = data_text.substitute({'python_data': json.dumps(df.to_dict(orient='records'))})
print data_text
# 以下のコマンドを実行して、javascriptのコンソールをみてください。d3で読み込んだ時と同じログが出力されています。
# In[8]:
HTML(data_text)
# ## スケールマッピング
# D3のスケール処理はとても良くできており、データに応じて選べるようになっています。
# - d3.scale.linear(): 線形補間
# - d3.scale.quantile(): 分位数(区間で分けられた値)
#
# 最初に、線形補間を見てみましょう。
# domain関数では、問題領域(ここではデータの分布領域)をrange関数で指定された範囲にマッピングします。
#
# console.logの代わりにelement.textを使うとjupyterのノートブックに出力できます(ただし1回だけ指定可能みたい)。
#
# In[9]:
get_ipython().run_cell_magic('javascript', '', 'var newRamp = d3.scale.linear().domain([500000,13000000]).range([0, 500]);\nelement.text(newRamp(1000000) + ", " +newRamp(9000000)+", "+newRamp.invert(313));\n')
# D3のscaleの凄いのは、数値だけでなくカラーにもマッピングできることです。
# In[10]:
get_ipython().run_cell_magic('javascript', '', 'var newRamp = d3.scale.linear().domain([500000,13000000]).range(["blue","red"]);\nelement.text(newRamp(1000000) + ", " +newRamp(9000000));\n')
# ### カテゴリわけ
# 次にquantile関数を使っていくつかのカテゴリに分けてみます。
#
# 以下の例では、sampleArrayのデータを3つのグループに分けて、small, medium, largetとします。
# 値ではなく、ソートした後にデータ数が等分になるようにカテゴリわけしているみたいです。
#
# [1,10,44], [58,66,124], [423, 524, 900]から
#
# (-∞..44], (44..124], (124..∞)の区間をsmall, medium, largeにマッピングしています。
#
# In[11]:
get_ipython().run_cell_magic('javascript', '', 'var sampleArray = [423,124,66,424,58,10,900,44,1];\nvar qScaleName =\nd3.scale.quantile().domain(sampleArray).range(["small", "medium","large"]);\nelement.text(qScaleName(68) + ", " +qScaleName(20)+", "+qScaleName(10000));\n')
# ## データバインディング
# D3.jsの最も重要な機能は、データバインディングだと思います。
#
# 1章で見たのはenterメソッドでしたが、exit, updateについてその動きを確認してみましょう。
#
# update, enter, exitの違いが、Fig. 2.24に解説されているので、引用します。
#
# ![Fig. 2.24](images/Fig_2.24.png)
#
#
# バインディングする要素よりもデータが多い場合のenterの動作を見てみましょう。
# In[12]:
get_ipython().run_cell_magic('HTML', '', '
\n')
# In[13]:
get_ipython().run_cell_magic('javascript', '', 'var sampleData = [1, 2, 3, 4];\n\nd3.select(\'#ex1\').selectAll(\'div\')\n .data(sampleData)\n .enter()\n .append("div")\n .html(function (d) { return d; })\n')
# この例では、1個のdivに対してデータの1がバインディングされて、残りの2, 3, 4に対しては新たにdiv要素を追加し、そこにデータの値をhtmlでセットします。
#
# 結果は期待に反し、2, 3, 4だけが表示されてました。最初のdivに対しては何も処理をしていないため、このようになります。
#
# それでは、最初の1に対してもhtmlの処理を追加してみましょう。
#
# In[14]:
get_ipython().run_cell_magic('HTML', '', '\n')
# In[15]:
get_ipython().run_cell_magic('javascript', '', 'var sampleData = [1, 2, 3, 4];\n\nd3.select(\'#ex2\').selectAll(\'div\')\n .data(sampleData)\n .html(function (d) { return d; })\n .enter()\n .append("div")\n .html(function (d) { return d; });\n')
# これでは同じ処理を2度書かなくてはなりません。
# そこで、最初にdivを削除してから処理をします。
# In[16]:
get_ipython().run_cell_magic('javascript', '', 'var sampleData = [1, 2, 3, 4];\n// remove all divs under #ex2\nd3.select(\'#ex2\').selectAll(\'div\').remove();\n\nd3.select(\'#ex2\').selectAll(\'div\')\n .data(sampleData)\n .enter()\n .append("div")\n .html(function (d) { return d; });\n')
# ### exitによる削除
# 次に要素よりもデータが少ない場合に使用するexitを試してみます。
#
# 4個のdiv要素にa, b, c, dがセットされているところに、1, 2の2個の要素をバインディングします。
#
# In[17]:
get_ipython().run_cell_magic('HTML', '', '\n')
# In[18]:
get_ipython().run_cell_magic('javascript', '', "var sampleData = [1, 2];\n\nd3.select('#ex3').selectAll('div')\n .data(sampleData)\n .html(function (d) { return d; })\n .exit()\n .remove();\n")
# ## 棒グラフの表示
# svgタグに棒グラフを描いてみましょう。
#
# - rect要素を追加し、xを10pxずつ移動し、yをデータの値*10pxで表示
# In[19]:
get_ipython().run_cell_magic('HTML', '', "\n \n
\n")
# In[20]:
get_ipython().run_cell_magic('javascript', '', 'd3.select(\'#ex4\').select("svg")\n .selectAll("rect")\n .data([15, 50, 22, 8, 100, 10])\n .enter()\n .append("rect")\n .attr("width", 10)\n .attr("height", function(d) {return d;})\n .style("fill", "blue")\n .style("stroke", "red")\n .style("stroke-width", "1px")\n .style("opacity", .25)\n .attr("x", function(d, i) {return i * 10})\n .attr("y", function(d) {return 100 - d;});\n')
# ## CSVデータから棒グラフを作る
# 2章のメインテーマは、CSVファイルから棒グラフを作成することです。
#
# 例題にしたがって、cities.csvから棒グラフを作ってみます。
# In[21]:
get_ipython().run_cell_magic('HTML', '', "\n \n
\n")
# In[22]:
get_ipython().run_cell_magic('javascript', '', '// dataフォルダのcities.csvを読み込み、dataViz関数を呼び出す\nd3.csv("data/cities.csv",function(error,data) {dataViz(data);});\nfunction dataViz(incomingData) {\n var maxPopulation = d3.max(incomingData, function(el) {\n // 人口のデータを文字列から数値に変換\n return parseInt(el.population);}\n );\n // 人口の最大値を0-230の範囲にスケーリングするyScaleを作成\n var yScale = d3.scale.linear().domain([0,maxPopulation]).range([0,230]);\n // 棒グラフの作成\n d3.select(\'#ex5\').select("svg").attr("style","height: 240px; width: 300px;");\n d3.select("#ex5 svg")\n .selectAll("rect")\n .data(incomingData)\n .enter()\n .append("rect")\n .attr("width", 25)\n .attr("height", function(d) {return yScale(parseInt(d.population));})\n .attr("x", function(d,i) {return i * 30;})\n .attr("y", function(d) {return 240 - yScale(parseInt(d.population));})\n .style("fill", "blue")\n .style("stroke", "red")\n .style("stroke-width", "1px")\n .style("opacity", .25);\n}\n')
# In[ ]: