#!/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
\n
\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
\n
\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
a
\n
b
\n
c
\n
d
\n
\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[ ]: