GitHubのアレをぐるぐるさせてみた

# はじめに

こちらの記事は GitHub Actions Advent Calendar 2021 の最終日の記事です。

https://qiita.com/advent-calendar/2021/github-actions

今回、こんなの(👇)を作る GitHub Action を作りました。

![night-rainbow](https://raw.githubusercontent.com/yoshi389111/github-profile-3d-contrib/main/docs/demo/profile-night-rainbow.svg)

（お時間があれば、10秒ぐらい眺めていてください）

# 経緯

実は今年の GW ごろに、GitHub Action を初めて作って、勢い余ってマーケットプレイスにも公開してしまいました。

その時のメモはこちら(👇)。

https://qiita.com/yoshi389111/items/4471c7a73f785fed4615

内容としては、GitHub の profile 用 SVG を自動で作成するコマンドです。

GitHub ではアカウント名と同じリポジトリを作ると、profile 用のリポジトリになるという仕様があります。

https://docs.github.com/ja/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme

その profile 用の画像を、自分の GitHub の情報をもとに GitHub Actions で自動生成するというツールです。
似たようなものはほかにもたくさんありますが、自分でつくってみたかったのです。

使用技術としては、以下のような感じ。

* GitHub Actions の種類は、JavaScriptアクション
* ソースは TypeScript で実装
* GitHub のコントリビュート情報の取得には GitHub GraphQL を利用
* SVG を生成するのには d3.js を利用

その時出来上がったのはこんなものです。

![green-animate](https://raw.githubusercontent.com/yoshi389111/github-profile-3d-contrib/main/docs/demo/profile-green-animate.svg)

GitHub の芝生を立体化した感じの物（👆）。
（ちなみにひそかに GitHub contributions calendar のハロウィンモードにも対応しています）

![season-animate](https://raw.githubusercontent.com/yoshi389111/github-profile-3d-contrib/main/docs/demo/profile-season-animate.svg)

さらにそれを、季節によって色を変化させた物（👆）。
春は花の色を、夏は緑、秋は紅葉、冬は雪をイメージしています。

ありがたいことに、何人かの人には実際に使ってもらえているようです。ありがとうございます。

さらには、国外の人からもいくつか star をもらったりしています。さすがは GitHub、グローバルな感じ。

……で、グローバルという観点でふと思いました。
「季節」って、南半球は逆じゃね？

## 南半球のseasonバージョンの対応

そこで、南半球の季節バージョンを対応することにしました。

![svg](https://raw.githubusercontent.com/yoshi389111/github-profile-3d-contrib/main/docs/demo/profile-south-season-animate.svg)

こんな感じです（👆）。

北半球の季節を半年ずらしているだけですね。

もちろん、色の設定を変えるだけなので簡単に対応ができます。

しかし、単純にモードの切り替えを実装すると、今後さらに様々な色のバージョンを作りたくなったときに、そのたびごとに処理の切替を追加する必要があると考えました。

そこで、内部の実装的には色のセット（パレット？）を渡して、それを使うように変更することにしました。

以下のような感じ。TypeScriptの型システムはあまり詳しくないのでもっと良い方法があるかもしれませんが。

```typescript
export interface NormalColorSettings {
    type: 'normal';
    backgroundColor: string;
    foregroundColor: string;
    strongColor: string;
    weakColor: string;
    radarColor: string;

    contribColors: [string, string, string, string, string];
}

export interface SeasonColorSettings {
    type: 'season';
    backgroundColor: string;
    foregroundColor: string;
    strongColor: string;
    weakColor: string;
    radarColor: string;

    contribColors1: [string, string, string, string, string];
    contribColors2: [string, string, string, string, string];
    contribColors3: [string, string, string, string, string];
    contribColors4: [string, string, string, string, string];
}

export type Settings =
    | NormalColorSettings
    | SeasonColorSettings;
```


## 夜景(night view)モードの対応

色のセットを設計する際に、どのような色をセットできるようにすべきか、あるいはパレット対応が漏れている箇所がないかどうか確認するために、もう一つのモードとして、暗い画面用のモードを作ってみました。
つまり、前景色と背景色の明暗が反転するようなイメージですね。

![night-view](https://raw.githubusercontent.com/yoshi389111/github-profile-3d-contrib/main/docs/demo/profile-night-view.svg)

こんな感じです（👆）。
（今までは、アニメーション版と非アニメーション版をそれぞれ用意していたのですが、面倒になって、これはアニメーション版しか生成していません。表示したら柱がぐーーんと伸びていくのがアニメーション版です）

これで、対応もれしている箇所（というか、そもそも色を指定していなかったところ）をいくつか見つけたりしました。

## アレなバージョンを追加

上記の夜景モードを見ていたら、ふと思いついたことがあります。

そう、天才的なアイデア、というよりは、悪魔的発想……。

つまり（カタカタ）
これを（カタカタ）
こうして（カタカタ）
ここを（カタカタカタ）
こうすれば（カタカタ）
こうだ！（カタカタ　カッターーーン）

![night-rainbow](https://raw.githubusercontent.com/yoshi389111/github-profile-3d-contrib/main/docs/demo/profile-night-rainbow.svg)

こう（👆）。

つまり、GitHub のアレをぐるぐるさせると、すごくアレっぽくなるから、やばいよね？（語彙力崩壊中）

お分かりだとは思いますが、技術的には難しいことはなくてHSL色空間をつかって、それっぽく色が変化するようにアニメーションをさせればよいだけなんですが、非常にアレっぽい感じになった気がします（個人の感想です）

もちろん、元の色のセット（パレット）では対応ができない項目が増えたので、このためにモードを１つ追加したりしましたが。

```typescript
export interface RainbowColorSettings {
    type: 'rainbow';
    backgroundColor: string;
    foregroundColor: string;
    strongColor: string;
    weakColor: string;
    radarColor: string;

    saturation: number;
    contribLightness: [string, string, string, string, string];
    duration: string; // ex. '10s'
    hueRatio: number; // hue per weeks
}

export type Settings =
    | NormalColorSettings
    | SeasonColorSettings
    | RainbowColorSettings;
```

アニメーション用のデータを作るのは（実際のコードとはちょっと違うけど）こんな感じ

```typescript
    const offsetHue = week * settings.hueRatio;
    const saturation = settings.saturation;
    const lightness = settings.contribLightness[contributionLevel];
    const values = [...Array<undefined>(7)]
        .map((_, i) => (i * 60 + offsetHue) % 360)
        .map((hue) => `hsl(${hue},${saturation}%,${lightness})`)
        .map((c) => d3.rgb(c).darker(darker).toString())
        .join(';');

    const path = group
        .append('path')
        .attr('d', plainLeft)
        .attr('stroke-width', '0px');
        .append('animate')
        .attr('attributeName', 'fill')
        .attr('values', values)
        .attr('dur', settings.duration)
        .attr('repeatCount', 'indefinite');
```

最初は `hsl(0,xx%,xx%);hsl(360,xx%,xx%)` と指定したらアニメーションするかと思ったら、アニメーションにならなかった（赤色一色のままだった。アニメーションの属性を変えたらうまくいくのかな？）ので、適当に60度ごとに色を指定しているイメージです。
（60度ごとに指定していますが、最初と最後を同じにするため、6か所ではなく7か所指定しています）

また、コントリビュートレベルで色を変えている（コミット回数によって、芝生の色を4段階に変えている）のは、彩度を変化させています。

ブロック（柱）の上面、右面、左面で色を変えているのは、ほかの色モードと平仄を合わせて `darker()` を使っていますが、意味としては輝度を変えていることになるのかな？（よーしらん）

# 最後に

というわけで、初めて Advent Calendar に参加しようとして、慌ててでっち上げt……、もとい実装した機能の紹介でした。

https://github.com/yoshi389111/github-profile-3d-contrib

もし、気が向いたら上記リポジトリをフォークして、少しいじって自分だけのプロフィールを作るのもおすすめですよ。