概要
ReactでGitHubの草(Contribution)を再現してみる記事になります。
使用するライブラリ
@uiw/react-heat-map を使います。
他にもReactでヒートマップを作成できるライブラリはあるのですが、2024年12月現在、定期的に更新されているライブラリとしてこちらを選定しました。
実装
Heatmap.tsx
import { ContributionValue, GitHubContributionWeeks } from "@/types/contribution";
import HeatMap from '@uiw/react-heat-map';
import { useEffect, useState } from "react";
export const Heatmap = () => {
const nowDate = new Date();
const heatmapStartDate = nowDate.setMonth(nowDate.getMonth() - 4);
const [heatmapValue, setHeatmapValue] = useState<ContributionValue[]>([{date: heatmapStartDate.toString(), count: 0}]);
// GitHub APIに送信する情報
const USERNAME = process.env.GITHUB_USERNAME;
const headers = {
'Authorization': `bearer ${process.env.GITHUB_TOKEN}`,
}
const body = {
"query": `query {
user(login: "${USERNAME}"){
contributionsCollection {
contributionCalendar {
totalContributions
weeks {
contributionDays {
contributionCount
date
}
}
}
}
}
}`
}
const generateContributionValues = (weeks: GitHubContributionWeeks) => {
const contributionValues: ContributionValue[] = [];
for (const week of weeks) {
week.contributionDays.map((obj) => {
contributionValues.push({count: obj.contributionCount, date: obj.date})
})
}
return contributionValues;
}
useEffect(() => {
// コンポーネント読み込み時にGitHubからContributionの情報を取得する
const fetchData = async () => {
const response = await fetch('https://api.github.com/graphql',
{
method: 'POST',
body: JSON.stringify(body),
headers: headers
});
return response;
};
(async() => {
const response = await fetchData();
const data = await response.json();
const weeks = data.data.user.contributionsCollection.contributionCalendar.weeks;
const contributionValues = generateContributionValues(weeks);
setHeatmapValue(contributionValues);
})();
}, []);
return (
<div className="heatmap">
<HeatMap
value={heatmapValue}
startDate={new Date(heatmapStartDate)}
endDate={new Date()}
panelColors={{
1: '#161b22',
2: '#0e4429',
3: '#006d32',
5: '#26a641',
8: '#39d353'
}}
/>
</div>
);
};
自身のGitHubからContribution情報をとってくる
.envファイルに自身のGitHubの認証情報を記載し、GitHub GraphQL APIでContribution情報を取得します。
const USERNAME = process.env.GITHUB_USERNAME;
const headers = {
'Authorization': `bearer ${process.env.GITHUB_TOKEN}`,
}
const body = {
"query": `query {
user(login: "${USERNAME}"){
contributionsCollection {
contributionCalendar {
totalContributions
weeks {
contributionDays {
contributionCount
date
}
}
}
}
}
}`
}
コンポーネント読み込み時にGitHub GraphQL APIを呼び出します。
useEffect(() => {
// コンポーネント読み込み時にGitHubからContributionの情報を取得する
const fetchData = async () => {
const response = await fetch('https://api.github.com/graphql',
{
method: 'POST',
body: JSON.stringify(body),
headers: headers
});
return response;
};
(async() => {
const response = await fetchData();
const data = await response.json();
const weeks = data.data.user.contributionsCollection.contributionCalendar.weeks;
const contributionValues = generateContributionValues(weeks);
setHeatmapValue(contributionValues);
})();
}, []);
APIのレスポンスからContributionの情報をヒートマップで利用できるように成型してあげます。
const generateContributionValues = (weeks: GitHubContributionWeeks) => {
const contributionValues: ContributionValue[] = [];
for (const week of weeks) {
week.contributionDays.map((obj) => {
contributionValues.push({count: obj.contributionCount, date: obj.date})
})
}
return contributionValues;
}
/* 以下のようなオブジェクトの配列に成型する
[
{
count: 0
date: "2024-12-01"
},
{
count: 1
date: "2024-12-02"
},
...
]
*/
heatmapValue
に成型したデータをsetします。
setHeatmapValue(contributionValues);
ヒートマップ
panelColors
プロパティで、Contributionの数でパネルの色を変化させることができます。
return (
<div className="heatmap">
<HeatMap
value={heatmapValue} // GitHubから取得したContributionデータ(成型済)をvalueとして持たせる
startDate={new Date(heatmapStartDate)}
endDate={new Date()}
panelColors={{
1: '#161b22',
2: '#0e4429',
3: '#006d32',
5: '#26a641',
8: '#39d353'
}}
/>
</div>
);
GitHubのContributionを再現することができました!
おまけ: Tooltipの実装
react-tooltip を活用すれば、ホバー時にContribution数を表示させることができます。
import { Tooltip } from 'react-tooltip';
return (
<div className={styles["activity"]}>
<HeatMap
value={heatmapValue}
startDate={new Date(heatmapStartDate)}
endDate={new Date()}
panelColors={{
1: '#161b22',
2: '#0e4429',
3: '#006d32',
5: '#26a641',
8: '#39d353'
}}
rectRender={(props, data) => {
if (!data.count) return <rect {...props} />;
return (
<>
<rect data-tooltip-id={`heatmap`} onMouseEnter={() => setTooltipData({...data})} {...props} />
</>
);
}}
/>
<Tooltip id={`heatmap`} content={`count: ${tooltipData.count} date: ${tooltipData.date}`}/>
</div>
);