{"meta":{"title":"データをグラフとしてレンダリングする","intro":"D3.jsライブラリとRuby Octokitを使用して、リポジトリからプログラミング言語を視覚化する方法を学びましょう。","product":"REST API","breadcrumbs":[{"href":"/ja/rest","title":"REST API"},{"href":"/ja/rest/guides","title":"ガイド"},{"href":"/ja/rest/guides/rendering-data-as-graphs","title":"データをグラフとしてレンダリングする"}],"documentType":"article"},"body":"# データをグラフとしてレンダリングする\n\nD3.jsライブラリとRuby Octokitを使用して、リポジトリからプログラミング言語を視覚化する方法を学びましょう。\n\nこのガイドでは、API を使用して、所有するリポジトリと、それを構成するプログラミング言語についての情報を取得します。 次に、[D3.js](https://d3js.org/) ライブラリを使用して、その情報をいくつかの方法で視覚化します。 GitHub API を操作するには、優れた Ruby ライブラリである [Octokit](https://github.com/octokit/octokit.rb) を使います。\n\nまだ「[認証の基本](/ja/apps/oauth-apps/building-oauth-apps/authenticating-to-the-rest-api-with-an-oauth-app)」を読んでいない場合は、読んでから以下の例に取り組んでください。 このプロジェクトの完全なソース コードは、[platform-samples](https://github.com/github/platform-samples/tree/master/api/ruby/rendering-data-as-graphs) リポジトリにあります。\n\nそれでは早速始めましょう！\n\n## OAuth app の設定\n\n最初に、GitHub で[新しいアプリケーションを登録](https://github.com/settings/applications/new)します。 メイン URL とコールバック URL は `http://localhost:4567/` に設定してください。\n[以前](/ja/apps/oauth-apps/building-oauth-apps/authenticating-to-the-rest-api-with-an-oauth-app)と同様に、[sinatra-auth-github](https://rubygems.org/gems/sinatra_auth_github) を使用して Rack ミドルウェアを実装することで、API の認証を処理します。\n\n```ruby\nrequire 'sinatra/auth/github'\n\nmodule Example\n  class MyGraphApp < Sinatra::Base\n    # !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!!\n    # Instead, set and test environment variables, like below\n    # if ENV['GITHUB_CLIENT_ID'] && ENV['GITHUB_CLIENT_SECRET']\n    #  CLIENT_ID        = ENV['GITHUB_CLIENT_ID']\n    #  CLIENT_SECRET    = ENV['GITHUB_CLIENT_SECRET']\n    # end\n\n    CLIENT_ID = ENV['GH_GRAPH_CLIENT_ID']\n    CLIENT_SECRET = ENV['GH_GRAPH_SECRET_ID']\n\n    enable :sessions\n\n    set :github_options, {\n      :scopes    => \"repo\",\n      :secret    => CLIENT_SECRET,\n      :client_id => CLIENT_ID,\n      :callback_url => \"/\"\n    }\n\n    register Sinatra::Auth::Github\n\n    get '/' do\n      if !authenticated?\n        authenticate!\n      else\n        access_token = github_user[\"token\"]\n      end\n    end\n  end\nend\n```\n\n前の例と同様の *config.ru* ファイルを設定します。\n\n```ruby\nENV['RACK_ENV'] ||= 'development'\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\nrequire File.expand_path(File.join(File.dirname(__FILE__), 'server'))\n\nrun Example::MyGraphApp\n```\n\n## リポジトリ情報の取得\n\n今回は、GitHub API とやり取りするために、[Octokit Ruby ライブラリ](https://github.com/octokit/octokit.rb)を使います。 これは、多くの REST 呼び出しを直接行うよりもはるかに簡単です。 さらに、Octokit は GitHubber によって開発され、積極的にメンテナンスされているので、確実に動作します。\n\nOctokit経由のAPIによる認証は簡単です。 ログインとトークンを `Octokit::Client` コンストラクターに渡すだけです。\n\n```ruby\nif !authenticated?\n  authenticate!\nelse\n  octokit_client = Octokit::Client.new(:login => github_user.login, :oauth_token => github_user.token)\nend\n```\n\nリポジトリに関するデータを使って面白いことをしてみましょう。 使用されているさまざまなプログラミング言語を表示し、最も多く使われている言語をカウントします。 そのためには、まずAPIからリポジトリのリストを取得する必要があります。\nOctokitでは、次のようにします。\n\n```ruby\nrepos = client.repositories\n```\n\n次に、各リポジトリで処理を繰り返し、GitHub によってリポジトリに関連付けられた言語をカウントします。\n\n```ruby\nlanguage_obj = {}\nrepos.each do |repo|\n  # sometimes language can be nil\n  if repo.language\n    if !language_obj[repo.language]\n      language_obj[repo.language] = 1\n    else\n      language_obj[repo.language] += 1\n    end\n  end\nend\n\nlanguages.to_s\n```\n\nサーバーを再起動すると、Web ページに以下のような内容が表示されます。\n\n```ruby\n{\"JavaScript\"=>13, \"PHP\"=>1, \"Perl\"=>1, \"CoffeeScript\"=>2, \"Python\"=>1, \"Java\"=>3, \"Ruby\"=>3, \"Go\"=>1, \"C++\"=>1}\n```\n\nここまではうまくいきましたが、人間に優しくないですね。 これらの言語のカウントがどのような分布になっているのかを把握するには、視覚化が非常に役立ちます。 カウントを D3 にフィードして、使用されている言語の人気を表す整った棒グラフを取得しましょう。\n\n## 言語の数を視覚化する\n\nD3.js (単にD3と表記することもある) は、多様なチャート、グラフ、インタラクティブな視覚化を作成するための包括的なライブラリです。\nD3 を細部にわたって使うことは、このガイドで扱う範囲を超えていますが、良い入門記事として「[D3 for Mortals](http://recursion.org/d3-for-mere-mortals/)」(凡人のための D3) を参照してください。\n\nD3はJavaScriptのライブラリで、データを配列として扱うことを好みます。 そこで、ブラウザーの JavaScript で使用するために、Ruby のハッシュを JSON 配列に変換してみましょう。\n\n```ruby\nlanguages = []\nlanguage_obj.each do |lang, count|\n  languages.push :language => lang, :count => count\nend\n\nerb :lang_freq, :locals => { :languages => languages.to_json}\n```\n\nここでは単純にオブジェクトのキーと値の各ペアを繰り返し処理して、新しい配列に入れ込んでいます。 これを以前に行わなかったのは、`language_obj` オブジェクトの作成時にオブジェクトを繰り返し処理したくなかったからです。\n\nここで、棒グラフのレンダリングをサポートするために、*lang\\_freq.erb* には何らかの JavaScript が必要となります。\nさしあたっては、ここで提供されているコードを使用できます。D3 の詳しいしくみについて確認する場合は、上記のリンクの資料をご覧ください。\n\n```html\n<!DOCTYPE html>\n<meta charset=\"utf-8\">\n<html>\n  <head>\n    <script src=\"//cdnjs.cloudflare.com/ajax/libs/d3/3.0.1/d3.v3.min.js\"></script>\n    <style>\n    svg {\n      padding: 20px;\n    }\n    rect {\n      fill: #2d578b\n    }\n    text {\n      fill: white;\n    }\n    text.yAxis {\n      font-size: 12px;\n      font-family: Helvetica, sans-serif;\n      fill: black;\n    }\n    </style>\n  </head>\n  <body>\n    <p>Check this sweet data out:</p>\n    <div id=\"lang_freq\"></div>\n\n  </body>\n  <script>\n    var data = <%= languages %>;\n\n    var barWidth = 40;\n    var width = (barWidth + 10) * data.length;\n    var height = 300;\n\n    var x = d3.scale.linear().domain([0, data.length]).range([0, width]);\n    var y = d3.scale.linear().domain([0, d3.max(data, function(datum) { return datum.count; })]).\n      rangeRound([0, height]);\n\n    // add the canvas to the DOM\n    var languageBars = d3.select(\"#lang_freq\").\n      append(\"svg:svg\").\n      attr(\"width\", width).\n      attr(\"height\", height);\n\n    languageBars.selectAll(\"rect\").\n      data(data).\n      enter().\n      append(\"svg:rect\").\n      attr(\"x\", function(datum, index) { return x(index); }).\n      attr(\"y\", function(datum) { return height - y(datum.count); }).\n      attr(\"height\", function(datum) { return y(datum.count); }).\n      attr(\"width\", barWidth);\n\n    languageBars.selectAll(\"text\").\n      data(data).\n      enter().\n      append(\"svg:text\").\n      attr(\"x\", function(datum, index) { return x(index) + barWidth; }).\n      attr(\"y\", function(datum) { return height - y(datum.count); }).\n      attr(\"dx\", -barWidth/2).\n      attr(\"dy\", \"1.2em\").\n      attr(\"text-anchor\", \"middle\").\n      text(function(datum) { return datum.count;});\n\n    languageBars.selectAll(\"text.yAxis\").\n      data(data).\n      enter().append(\"svg:text\").\n      attr(\"x\", function(datum, index) { return x(index) + barWidth; }).\n      attr(\"y\", height).\n      attr(\"dx\", -barWidth/2).\n      attr(\"text-anchor\", \"middle\").\n      text(function(datum) { return datum.language;}).\n      attr(\"transform\", \"translate(0, 18)\").\n      attr(\"class\", \"yAxis\");\n  </script>\n</html>\n```\n\nお疲れさまでした。 このコードが何をしているか詳しく知る必要はありません。 ここで重要なのは、一番上の行の `var data = <%= languages %>;` の部分です。これは、以前に作成した `languages` の行列を、操作のため ERB に渡すことを示しています。\n\n「一般の方向けの D3」ガイドで示されているように、これは必ずしも D3 の最善の利用法ではありません。 しかし、Octokit と一緒にライブラリを使用して、本当に素晴らしいものを作る方法を説明するのには役立ちます。\n\n## さまざまなAPI呼び出しの組み合わせ\n\nここで、告白をしなくてはいけません。リポジトリ内の `language` 属性が識別するのは、\"プライマリ\" 言語として定義されたものだけです。 つまり、複数の言語を組み合わせたリポジトリでは、コードのバイト数が最も多い言語がプライマリ言語と見なされます。\n\nいくつかの API 呼び出しを組み合わせて、コード全体で記述されたバイト数が最も多い言語を *正確に* 表したものを取得してみましょう。 使用されているコード言語のサイズを視覚化する方法としては、単純なカウントよりも[ツリーマップ](https://www.d3-graph-gallery.com/treemap.html)の方が適しています。 次のようなオブジェクトの配列を構築する必要があります。\n\n```json\n[ { \"name\": \"language1\", \"size\": 100},\n  { \"name\": \"language2\", \"size\": 23}\n  ...\n]\n```\n\n既に上でリポジトリのリストを取得しているので、それぞれを調べて、[GET /repos/{owner}/{repo}/languages endpoint](/ja/rest/repos/repos#list-repository-languages) を呼び出しましょう。\n\n```ruby\nrepos.each do |repo|\n  repo_name = repo.name\n  repo_langs = octokit_client.languages(\"#{github_user.login}/#{repo_name}\")\nend\n```\n\nそこから、見つかった各言語を言語のリストに次々に追加していきます。\n\n```ruby\nrepo_langs.each do |lang, count|\n  if !language_obj[lang]\n    language_obj[lang] = count\n  else\n    language_obj[lang] += count\n  end\nend\n```\n\nそれから、コンテンツをD3が理解できる構造にフォーマットします。\n\n```ruby\nlanguage_obj.each do |lang, count|\n  language_byte_count.push :name => \"#{lang} (#{count})\", :count => count\nend\n\n# some mandatory formatting for D3\nlanguage_bytes = [ :name => \"language_bytes\", :elements => language_byte_count]\n```\n\n(D3 のツリーマップの魅力について詳しくは、[このシンプルなチュートリアル](/ja/rest/repos/repos#list-repository-languages)をご確認ください。)\n\n仕上げに、このJSON情報を同じERBテンプレートに渡します。\n\n```ruby\nerb :lang_freq, :locals => { :languages => languages.to_json, :language_byte_count => language_bytes.to_json}\n```\n\n前と同じように、テンプレートに直接取り入れることができる一連の JavaScript を以下に示します。\n\n```html\n<div id=\"byte_freq\"></div>\n<script>\n  var language_bytes = <%= language_byte_count %>\n  var childrenFunction = function(d){return d.elements};\n  var sizeFunction = function(d){return d.count;};\n  var colorFunction = function(d){return Math.floor(Math.random()*20)};\n  var nameFunction = function(d){return d.name;};\n\n  var color = d3.scale.linear()\n              .domain([0,10,15,20])\n              .range([\"grey\",\"green\",\"yellow\",\"red\"]);\n\n  drawTreemap(5000, 2000, '#byte_freq', language_bytes, childrenFunction, nameFunction, sizeFunction, colorFunction, color);\n\n  function drawTreemap(height,width,elementSelector,language_bytes,childrenFunction,nameFunction,sizeFunction,colorFunction,colorScale){\n\n      var treemap = d3.layout.treemap()\n          .children(childrenFunction)\n          .size([width,height])\n          .value(sizeFunction);\n\n      var div = d3.select(elementSelector)\n          .append(\"div\")\n          .style(\"position\",\"relative\")\n          .style(\"width\",width + \"px\")\n          .style(\"height\",height + \"px\");\n\n      div.data(language_bytes).selectAll(\"div\")\n          .data(function(d){return treemap.nodes(d);})\n          .enter()\n          .append(\"div\")\n          .attr(\"class\",\"cell\")\n          .style(\"background\",function(d){ return colorScale(colorFunction(d));})\n          .call(cell)\n          .text(nameFunction);\n  }\n\n  function cell(){\n      this\n          .style(\"left\",function(d){return d.x + \"px\";})\n          .style(\"top\",function(d){return d.y + \"px\";})\n          .style(\"width\",function(d){return d.dx - 1 + \"px\";})\n          .style(\"height\",function(d){return d.dy - 1 + \"px\";});\n  }\n</script>\n```\n\nこれで一丁あがり! リポジトリの言語を含む美しい長方形が示されます。大きさは言語の割合に比例していて、一目でわかりやすくなっています。 すべての情報を正しく表示するためには、上記で渡した `drawTreemap` に対する最初の 2 つの引数で、ツリーマップの高さと幅の調整が必要になる場合があります。"}