- PVSM.RU - https://www.pvsm.ru -
Будучи поклонником программных продуктов для визуализации активности в репозиториях таких как code_swarm [1] и gource [2]. В один прекрасный день я был посещен музой, которая вдохновила меня создать онлайн сервис для визуализации статистики репозиториев с GitHub [3].
И сегодня хочу предоставить на ваш суд мой проект GitHub Visualizer [4] (проект на GitHub [5]).
Вот скринкаст для предварительного знакомства.
И не большая Gif'ка
В данном проекте есть три основных визуализации, демонстрирующие информацию о репозиториях, их истории и количественных показателях.
Для построения графа использовался D3.Layout.Force [13] и метод кластеризации предложенный в данном примере [14].
Кусок кода из примераvar force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .on("tick", tick) .start(); function tick(e) { circle .each(cluster(10 * e.alpha * e.alpha)) .each(collide(.5)) .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); } // Move d to be adjacent to the cluster node. function cluster(alpha) { var max = {}; // Find the largest node for each cluster. nodes.forEach(function(d) { if (!(d.color in max) || (d.radius > max[d.color].radius)) { max[d.color] = d; } }); return function(d) { var node = max[d.color], l, r, x, y, i = -1; if (node == d) return; x = d.x - node.x; y = d.y - node.y; l = Math.sqrt(x * x + y * y); r = d.radius + node.radius; if (l != r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; node.x += x; node.y += y; } }; } // Resolves collisions between d and all other circles. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + radius.domain()[1] + padding, nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.color !== quad.point.color) * padding; if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; }
Собственно это и была та муза которая посетила меня.
Функции взяты практический без изменения за некоторыми исключениями и добавлениями.
Реализация функционала для визуализации списка репозиториев находиться в двух файлах repo.js [15] и langHg.js [16]
После того как вы загрузите информацию о списке репозиториев пользователя вы можете выбрать интересующий вас репозиторий или в графе, или в списке репозиториев в панели второго этапа (также здесь можно задать кол-во последних ревизий для анализа).
Затем выполнить его анализ нажатием кнопки «Analyze». Вовремя анализа построится график истории репозитория. На котором отображается информация по указанному вами количеству последних коммитов (по-умолчанию 100 коммитов. Может быть и меньше сколько есть в репозитории).
График истории
[17]
Для того чтоб отрисовать диаграммы я использовал ряд средств и их комбинацию из библиотеки d3.js.
Вычисление областей выполняет компонент d3.svg.area() [18] (пример Stacked Area [19]). Стек я считаю сам, но все остальное тривиально для d3js.Кусок кода где считается стекvar layers = [ { color: colors.deletedFile, values: sorted.map(function (d) { return {t : 1, x: d.date, y0 : 0, y: (d.stats ? -d.stats.f.d : 0)} }) }, { color: colors.modifiedFile, values: sorted.map(function (d) { return {x: d.date, y0 : 0, y: (d.stats ? d.stats.f.m : 0)} }) }, { color: colors.addedFile, values: sorted.map(function (d) { return {x: d.date, y0: (d.stats ? d.stats.f.m : 0), y : (d.stats ? d.stats.f.a : 0)} }) } ] ; function interpolateSankey(points) { var x0 = points[0][0], y0 = points[0][1], x1, y1, x2, path = [x0, ",", y0], i = 0, n = points.length; while (++i < n) { x1 = points[i][0]; y1 = points[i][1]; x2 = (x0 + x1) / 2; path.push("C", x2, ",", y0, " ", x2, ",", y1, " ", x1, ",", y1); x0 = x1; y0 = y1; } return path.join(""); } var y1 = d3.scale.linear() .range([h6 * 4.5, h6 * 3, h6 * 1.5]) .domain([-data.stats.files, 0, data.stats.files]), area = d3.svg.area() .interpolate(interpolateSankey /*"linear" "basis"*/) .x(function(d) { return x(d.x); }) .y0(function(d) { return y1(d.y0); }) .y1(function(d) { return y1(d.y0 + d.y); }) ;
Для построения дуг использую d3.svg.arc() [18] (есть множество примеров где используется данный компонент: Arc Tween [20], Pie Multiples [21]).
Генерацию шкалы X делаю с использованием двух компонентов d3.time.scale() [22] и d3.svg.axis [23]. Реализация взята из этого примера Custom Time Format [24].
Диаграмму участников просчитывает d3.layout.pack() [25] (пример Circle Packing [26]). Для того чтоб сортировать и изменять размер кругов я меняю свойства sort и value.
Код для данной визуализации располагается в двух файлах stat.js [27] и usercommit.js [28]
Ради этого все и была вся затея. Мне нравится что получается при визуализации с использование code_swarm [1], но каждый раз клонировать репозиторий к себе на компьютер а затем его визуализировать доставляет не удобство.
В данной визуализации я постарался воплотить все идеи которые применяются в code_swarm и сделать изменение настроек на лету.
Визуализация song-of-github [29], Ссылка для запуска [30], Статья о Song-of-github на хабрахабре [31]
Расчет физики выполняет пресловутый D3.Layout.Force [13], но с небольшим упущением их два. Один рассчитывает позиции пользователей, другой считает положение файлов в зависимости от положения пользователя. Как это сделано? У каждого файла есть свойство
author
, в него записывает текущий на данный момент (момент коммита) пользователь если этот файл есть в текущей фиксации. Выше указанный метод кластеризации получает его и считает положение данного файла в пространстве.Функция кластеризацииfunction tick() { if (_force.nodes()) { _force.nodes() .forEach(cluster(0.025)); _forceAuthor.nodes( _forceAuthor.nodes() .filter(function(d) { blink(d, !d.links && setting.userLife > 0); if (d.visible && d.links === 0 && setting.userLife > 0) { d.flash = 0; d.alive = d.alive / 10; } return d.visible; }) ); } _forceAuthor.resume(); _force.resume(); } // Move d to be adjacent to the cluster node. function cluster(alpha) { authorHash.forEach(function(k, d) { d.links = 0; }); return function(d) { blink(d, setting.fileLife > 0); if (!d.author || !d.visible) return; var node = d.author, l, r, x, y; if (node == d) return; node.links++; x = d.x - node.x; y = d.y - node.y; l = Math.sqrt(x * x + y * y); r = radius(nr(d)) / 2 + (nr(node) + setting.padding); if (l != r) { l = (l - r) / (l || 1) * (alpha || 1); x *= l; y *= l; d.x -= x; d.y -= y; } }; }
И место инициализации force layout'ов
_force = (_force || d3.layout.force() .stop() .size([w, h]) .friction(.75) .gravity(0) .charge(function(d) {return -1 * radius(nr(d)); } ) .on("tick", tick)) .nodes([]) ; ..... _forceAuthor = (_forceAuthor || d3.layout.force() .stop() .size([w, h]) .gravity(setting.padding * .001) .charge(function(d) { return -(setting.padding + d.size) * 8; })) .nodes([]) ;
Работают два потока (если так можно сказать) один это
setInterval
другойrequestAnimationFrame
. Первый отвечает за перемещение по времени, второй за отрисовку. Но на самом деле еще и force имеют свои таймеры и asyncForEach (нужен для того чтоб был хороший отклик системы и файлы из одного коммита вылетали не все сразу, а с небольшой задержкой) тоже запускает setTimeout'ы.
Код можно посмотреть в файле show.js [32].
Данные получаю с api.github.com [33].
Получение данных происходит по методике JSONP [34].
Согласно API GitHub [11] необходимости в наличии Client_id
и Client_Secret
, но тогда лимит запросов будет в размере 60 для одного ip в час. По этому я создал приложение в настройках профиля на GitHub [35] и в запрос добавляется не обходимая информация об авторизации.
Это я к чему все… А к тому что ограничение для такого способа авторизации 5000 запросов в час, некоторые репозитории типа mc [36] имеют богатую историю. И если по ней пройтись хорошо, то лимит быстро исчерпывается, о чем вам скажет система. Если подобное произойдет вы можете указать в меню System settings с права client_id
и client_secret
вашего приложения (предварительно создав его если его еще нет).
У GitHub очень хорошие API, достаточно выполнить только один запрос допустим запросив информацию о пользователе https://api.github.com/users/{user}
все остальные ссылки будут в ответе. Причем если это много страничный запрос ( допустим получение перечня репозиториев, в ответе только информация по 10 репозиториям) то в объекте ответа в параметре meta
есть ссылка на следующую страницу с полным набором параметров авторизации.
В самом начале когда я стал делать проект это была игрушка для себя, собственно она такой и осталась. Если вы форкните мой репозитории [5] и найдете кучу ошибок или прикрутите что-то новенькое, то прошу оставьте Pull Request или напишите в Issues [39].
Приложение при разработке проверялось только в Google Chrome dev-m (нет я конечно явные косяки, которые были в других браузерах исправил), если вы знаете, как сделать его корректно работающим в вашем любимом браузере буду бесконечно благодарен.
Жду здоровой критики.
Благодарю за внимание!
P.S.
Некоторые интересные репозитории:
Автор: artzub
Источник [49]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/data-visualization/30625
Ссылки в тексте:
[1] code_swarm: https://code.google.com/p/codeswarm/
[2] gource: https://code.google.com/p/gource/
[3] GitHub: https://github.com/
[4] GitHub Visualizer: http://ghv.artzub.com
[5] проект на GitHub: https://github.com/artzub/GitHubCodeSwarm
[6] SVG: http://ru.wikipedia.org/wiki/SVG
[7] Canvas: http://ru.wikipedia.org/wiki/Canvas
[8] D3.js: http://d3js.org
[9] Гигантская коллекция примеров: http://bl.ocks.org/mbostock
[10] Mike Bostock: http://bost.ocks.org/mike/
[11] API GitHub: http://developer.github.com/
[12] Image: http://artzub.com/ghv/article/vis_repo.png
[13] D3.Layout.Force: https://github.com/mbostock/d3/wiki/Force-Layout
[14] данном примере: http://bl.ocks.org/mbostock/1747543
[15] repo.js: https://github.com/artzub/GitHubCodeSwarm/blob/master/repo.js
[16] langHg.js: https://github.com/artzub/GitHubCodeSwarm/blob/master/langhg.js
[17] Image: http://artzub.com/ghv/article/vis_history.png
[18] d3.svg.area(): https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-arc
[19] Stacked Area: http://bl.ocks.org/mbostock/3020685
[20] Arc Tween: http://bl.ocks.org/mbostock/5100636
[21] Pie Multiples: http://bl.ocks.org/mbostock/1305111
[22] d3.time.scale(): https://github.com/mbostock/d3/wiki/Time-Scales
[23] d3.svg.axis: https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis
[24] Custom Time Format: http://bl.ocks.org/mbostock/4149176
[25] d3.layout.pack(): https://github.com/mbostock/d3/wiki/Pack-Layout
[26] Circle Packing: http://bl.ocks.org/mbostock/4063530
[27] stat.js: https://github.com/artzub/GitHubCodeSwarm/blob/master/stat.js
[28] usercommit.js: https://github.com/artzub/GitHubCodeSwarm/blob/master/usercommit.js
[29] song-of-github: https://github.com/ajacksified/song-of-github
[30] Ссылка для запуска: http://artzub.com/ghv/#repo=song-of-github&climit=100&user=ajacksified
[31] Статья о Song-of-github на хабрахабре: http://habrahabr.ru/post/173085/
[32] show.js: https://github.com/artzub/GitHubCodeSwarm/blob/master/show.js
[33] api.github.com: https://api.github.com/
[34] JSONP: http://ru.wikipedia.org/wiki/JSON#JSONP_.26_JSONPP
[35] в настройках профиля на GitHub: https://github.com/settings/applications
[36] mc: https://github.com/MidnightCommander/mc
[37] богатую коллекцию примеров: https://github.com/mbostock/d3/wiki/Gallery
[38] очень полную документацию: https://github.com/mbostock/d3/wiki
[39] Issues: https://github.com/artzub/GitHubCodeSwarm/issues
[40] репозитории проекта: http://artzub.com/ghv/#repo=GitHubCodeSwarm&climit=100&user=artzub&run
[41] D3js: https://github.com/mbostock/d3
[42] запуск визуализации: http://artzub.com/ghv/#repo=d3&climit=100&user=mbostock&run
[43] Image: http://artzub.com/ghv/ss_d3.png
[44] jQuery: https://github.com/jquery/jquery
[45] запуск визуализации: http://artzub.com/ghv/#repo=jquery&climit=100&user=jquery&run
[46] Image: http://artzub.com/ghv/ss_jquery.png
[47] запуск визуализации: http://artzub.com/ghv/#repo=mc&climit=100&user=MidnightCommander&run
[48] Image: http://artzub.com/ghv/ss_mc.png
[49] Источник: http://habrahabr.ru/post/174375/
Нажмите здесь для печати.