Main photo

Бобылев Артём

Визуализация статистики гитхаб: github contributions graph

Недавно столкнулся с задачей самостоятельной визуализации статистики github-пользователя, чтобы «как на github».

В этой статье опишу своë решение.

Как получить данные

Через поиск «github contributions data», находится масса готовых решений. Например, github-contributions-api.

С помощью GET-запроса по адресу https://github-contributions-api.jogruber.de/v4/GITHUB_USERNAME можно получить статистику пользователя в следующем виде:

{
  "total": {
    "2016": 249,
    "2017": 785
  },
  "contributions": [
    {
      "date": "2016-01-01",
      "count": 1,
      "level": 1
    },
    {
      "date": "2016-01-02",
      "count": 0,
      "level": 0
    },
    ...
  ]
}

Есть возможность выбрать отдельный год:

https://github-contributions-api.jogruber.de/v4/GITHUB_USERNAME?y=2020

Как визуализировать

Будем использовать библиотеку cal-heatmap.

В <head> вставим:

<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/cal-heatmap/dist/cal-heatmap.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/cal-heatmap/dist/cal-heatmap.css">

Для отображения тепловой карты необходим элемент в <body>, например:

<div id="cal-heatmap"></div> 

Подготовим пользовательские данные:

async function fetchGithubData() {
	const response = await fetch('https://github-contributions-api.jogruber.de/v4/karpathy?y=last');
	const user_data = await response.json();
	return user_data;
}

Для раскраски тепловой карты значения разбиваются на группы с соответствующим цветом.

Этого можно добиться, используя значения level из ответа api, либо написав функцию разбиения:

function getClassBounds(data, nClasses) {
    let minValue = Math.min(...data);
    let maxValue = Math.max(...data);
    let classWidth = (maxValue - minValue) / nClasses;
    let classBounds = [minValue];
    for (let i = 1; i < nClasses; i++) {
        classBounds[i] = Math.ceil(minValue + (i * classWidth));
    }
    return classBounds;
}

На github используются 5 цветов ['rgb(235,237,240)', 'rgb(172,231,174)', 'rgb(105,193,110)', 'rgb(83,159,87)', 'rgb(56,108,62)'], первый из которых соответствует нулевому числу contributions, поэтому разбивка пойдет на 4 группы:

fetchGithubData().then(user_data => {
	user_data = user_data.contributions;
	let values = [];
	user_data.forEach( obj => obj["count"] != 0 ? values.push(obj["count"]):null);
	let domain = getClassBounds(values, 4);
	let first_date = user_data[0]["date"];

	var cal = new CalHeatmap();
	cal.paint({
	   itemSelector: "#cal-heatmap",
	    domain: {
	      type: 'month',
	      gutter: 4,
	      label: { text: '', textAlign: 'start', position: 'top' },
	    },
	    subDomain: { type: 'ghDay', radius: 2, width: 11, height: 11, gutter: 4 },
	    date: { start: new Date(first_date) },
	    range: 12,
	      data: {
		source: user_data,
		x: (datum) => +new Date(datum["date"]),
		y: (datum) => +datum["count"]
	      },
	    scale: {
	      color: {
		type: 'threshold',
		range: ['rgb(235,237,240)', 'rgb(172,231,174)', 'rgb(105,193,110)', 'rgb(83,159,87)', 'rgb(56,108,62)'],
		domain: domain,
	      },
	    },
	   },
	);
});

Итоговый результат:

github contributions graph without labels

Именно в таком виде я использовал статистику github contributions для своего проекта gitcloths. При желании, можно добавить дни и месяцы.

Пример того, как это сделать можно найти на сайте cal-heatmap. Не забудьте дополнить зависимости в <head>, иначе подписи не отобразятся:

<script src="https://unpkg.com/cal-heatmap/dist/plugins/CalendarLabel.min.js"></script>

И отобразить карту следующим образом:

var cal = new CalHeatmap();
cal.paint({
   itemSelector: "#cal-heatmap",
    domain: {
      type: 'month',
      gutter: 4,
      label: { text: 'MMM', textAlign: 'start', position: 'top' },
    },
    subDomain: { type: 'ghDay', radius: 2, width: 11, height: 11, gutter: 4 },
    date: { start: new Date(first_date) },
    range: 12,
      data: {
	source: user_data,
	x: (datum) => +new Date(datum["date"]),
	y: (datum) => +datum["count"]
      },
    scale: {
      color: {
	type: 'threshold',
	range: ['rgb(235,237,240)', 'rgb(172,231,174)', 'rgb(105,193,110)', 'rgb(83,159,87)', 'rgb(56,108,62)'],
	domain: domain,
      },
    },
   },
  [
    [
      CalendarLabel,
      {
        width: 30,
        textAlign: 'start',
        text: () => dayjs.weekdaysShort().map((d, i) => (i % 2 == 0 ? '' : d)),
        padding: [25, 0, 0, 0],
      },
    ],
  ]
);

github contributions graph with labels

Итоговая карта – svg элемент. Для gitcloths мне нужно было именно это.

Тем не менее существует масса гораздо более простых решений. Например, вы можете внедрить github статистику на сайт, просто добавив img элемент с соответствующим src, используя ghchart.rshah.org.