{"meta":{"title":"Отрисовка данных в виде диаграмм","intro":"Узнайте, как визуализировать языки программирования из репозитория с помощью библиотек D3.js и Ruby Octokit.","product":"REST API","breadcrumbs":[{"href":"/ru/enterprise-server@3.22/rest","title":"REST API"},{"href":"/ru/enterprise-server@3.22/rest/guides","title":"Guides"},{"href":"/ru/enterprise-server@3.22/rest/guides/rendering-data-as-graphs","title":"Отрисовка данных в виде диаграмм"}],"documentType":"article"},"body":"# Отрисовка данных в виде диаграмм\n\nУзнайте, как визуализировать языки программирования из репозитория с помощью библиотек D3.js и Ruby Octokit.\n\nВ этом руководстве мы используем API для получения сведений о принадлежащих нам репозиториях и языках программирования, которые в них применяются. Затем мы визуализируем эти сведения несколькими разными способами с помощью библиотеки [D3.js](https://d3js.org/). Для взаимодействия с API GitHub мы будем использовать отличную библиотеку Ruby, [Octokit](https://github.com/octokit/octokit.rb).\n\nЕсли вы еще не сделали этого, перед началом этого примера ознакомьтесь [с руководством по основам проверки подлинности](/ru/enterprise-server@3.22/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[Сначала зарегистрируйте новое приложение](https://github.com/settings/applications/new) на GitHub. Задайте `http://localhost:4567/` в качестве основного URL-адреса и URL-адреса обратного вызова. Как и [ранее](/ru/enterprise-server@3.22/apps/oauth-apps/building-oauth-apps/authenticating-to-the-rest-api-with-an-oauth-app), мы будем осуществлять проверку подлинности для API, реализовав ПО промежуточного слоя Rack с помощью [sinatra-auth-github](https://rubygems.org/gems/sinatra_auth_github):\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На этот раз для связи с API GitHub мы будем использовать библиотеку [](https://github.com/octokit/octokit.rb)Octokit Ruby. Это гораздо проще, чем напрямую совершать множество вызовов REST. Кроме того, библиотека Octokit была разработана пользователем GitHub и активно поддерживается, поэтому вы можете быть уверены, что она будет работать.\n\nПроверка подлинности в API с помощью Octokit выполняется очень просто. Просто передайте имя входа и токен в конструктор `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.\nС помощью Octokit это можно сделать так:\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После перезапуска сервера веб-страница должна выглядеть примерно так:\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 — это эффективная библиотека для создания диаграмм, графиков и интерактивных визуализаций множества типов.\nИспользование D3 подробно выходит за рамки этого руководства, но для хорошей вводной статьи ознакомьтесь [с D3 для Смертных](http://recursion.org/d3-for-mere-mortals/).\n\nD3 — это библиотека JavaScript, поэтому с данными предпочтительно работать в виде массивов. Поэтому давайте преобразуем хэш Ruby в массив JSON для использования на JavaScript в браузере.\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 for Mortals, это, вероятно, не лучшее применение для D3. Но оно позволяет продемонстрировать, как эту библиотеку можно использовать с Octokit для реализации потрясающих возможностей.\n\n## Сочетание разных вызовов API\n\nПора признаться кое в чем: атрибут `language` в репозитории указывает только язык, который определен как \"основной\". Это означает, что если есть репозиторий, в котором применяется несколько языков, основным считается язык, на котором написан наибольший объем кода.\n\nДавайте объединим несколько вызовов API, чтобы получить *истинное* представление о том, на каком языке написано наибольшая часть всего кода.\n[Диаграмма \"дерево\"](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Так как у нас уже есть список репозиториев выше, давайте проверим каждую из них и вызовем конечную точку [](/ru/enterprise-server@3.22/rest/repos/repos#list-repository-languages)GET /repos/{owner}/{repo}/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 см. в [этом простом учебнике](/ru/enterprise-server@3.22/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` выше."}