LoginSignup
14
10

More than 5 years have passed since last update.

GitHubの草生やすやつを作ってみた

Last updated at Posted at 2018-01-08

一人もくもく会の自分の投稿が増えてきたので、GitHubの草を生やすやつを作ってみると気分がいいのではないかと思い作成してみた。

こういうの

Screenshot from 2017-12-28 22-33-46.png

Vueで。
本家はsvgで描画しているらしい。とりあえず適当にテーブルで作った。

<template>
  <div class="panel panel-default">
    <div class="panel-body">
      <table>
        <tr v-for="(row, index) in contributionRows" :key="index">
          <th v-if="index === 0"></th>
          <th v-if="weekdayName(index)" rowspan="2">{{weekdayName(index)}}</th>
          <td
            v-for="(cell, cellIndex) in row"
            :key="cellIndex"
            v-bind:class="cell.getClass()"
            data-toggle="tooltip"
            data-animation="false"
            data-container="body"
            data-placement="top"
            data-html="true"
            title=""
            :data-original-title="cell.getMessage()"
          ></td>
        </tr>
      </table>
      <div class="note">
        <span class="string">Less</span>
        <span class="color bg-grey-200"></span>
        <span class="color bg-green-200"></span>
        <span class="color bg-green-400"></span>
        <span class="color bg-green-600"></span>
        <span class="color bg-green-800"></span>
        <span class="color bg-green-900"></span>
        <span class="string">More</span>
      </div>
    </div>
  </div>
</template>

<style scoped>
div.panel {
  margin-top: 40px;
}

table {
  border: 0;
  border-collapse: separate;
  border-spacing: 2px;
}

th {
  font-weight: lighter;
  font-size: 10px;
  transform: scale(0.9);
  transform-origin: 0 0;
  padding-right: 0.5rem;
  vertical-align: top;
}

td {
  width: 10px;
  height: 10px;
}

.note {
  margin-top: 1rem;
  text-align: right;
}

.note span.string {
  margin-left: 1rem;
  margin-right: 1rem;
  font-size: 9px;
}

.note span.color {
  display: inline-block;
  width: 10px;
  height: 10px;
}
</style>

<script>
import moment from 'moment'

class Cell {
  constructor(date, count) {
    this.date = date.clone();
    this.count = count;
  }

  getClass() {
    if (this.count === 0) {
      return 'bg-grey-200';
    } else if (this.count <= 2) {
      return 'bg-green-200';
    } else if (this.count <= 4) {
      return 'bg-green-400';
    } else if (this.count <= 6) {
      return 'bg-green-600';
    } else if (this.count <= 8) {
      return 'bg-green-800';
    }
    return 'bg-green-900';
  }

  getMessage() {
    return '<b>' + this.getCountMessage() + '</b> on ' + this.date.format('YYYY-MM-DD');
  }

  getCountMessage() {
    if (this.count === 0) {
      return 'No contributions';
    }
    return `${this.count} contributions`;
  }
}

export default {
  props: ['contributions'],

  data() {
    let contributionRows = [];
    for (let i = 0; i < 7; i++) {
      contributionRows[i] = [];
    }

    const today = moment().format('YYYY-MM-DD');
    const baseDate = moment().add(-364, 'days');
    const startDate = baseDate.add(-baseDate.weekday(), 'days');
    for (let date = startDate.clone(); date.format('YYYY-MM-DD') <= today; date = date.add(1, 'days')) {
      contributionRows[date.weekday()].push(new Cell(date, this.getCountForDate(date)));
    }

    return {contributionRows}
  },

  mounted() {
    $('[data-toggle="tooltip"]').tooltip();
  },

  methods: {
    getCountForDate(date) {
      const formatDate = date.format('YYYY-MM-DD');
      if (this.contributions[formatDate] !== undefined) {
        return this.contributions[formatDate];
      }
      return 0;
    },

    weekdayName(index) {
      if (index == 1) {
        return 'Mon';
      } else if (index == 3) {
        return 'Wed';
      } else if (index == 5) {
        return 'Fri';
      }
    }
  }
}
</script>

tooltip有効化のためだけにVueコンポーネントにするのはなんとなく微妙ではある…。

呼び出し。

<contribution-map :contributions="<?= h(json_encode($contributionMap)) ?>"></contribution-map>

集計処理。

Cake\Chronos\Dateで時間を考慮しない日付を扱える。時間が関わってくると意外に厄介だったりするので。(これくらいでは関係ないが)

UsersTable.php
    public function createContributionMap($id)
    {
        $end = Date::today();
        $start = Date::today()->subYear(1)->addDay(1);
        $start = $start->subDay($start->dayOfWeek);
        $summary = $this->Meetings->getDateSummary($id, $start, $end);

        return $summary;
    }

実際の集計。Meetingをクローズする際に必ずMeetingPostが追加されるので、MeetingPostがあればその数、なければ1を加えていく。

  • $postCountは通常のfind。CakePHP3はQueryになるので、そのまま集計クエリのサブクエリとして使うことができる。
  • $dateはMySQLのDATE_FORMATの処理。マニュアルにはちゃんとした説明がなかったのだが使ってみたら使えた。
MeetingsTable.php
    public function getDateSummary($userId, Date $start, Date $end)
    {
        $query = $this->find();
        $postCount = $this->MeetingPosts->find()
            ->select([$query->func()->count('*')])
            ->where([
                'MeetingPosts.meeting_id = Meetings.id',
                'MeetingPosts.deleted' => false,
            ]);
        $date = $query->func()->date_format([
            'created' => 'identifier',
            "'%Y-%m-%d'" => 'literal'
        ]);
        $summary = $query->select([
                'date' => $date,
                'post_count' => $postCount,
            ])
            ->where([
                'Meetings.user_id' => $userId,
                'Meetings.deleted' => false,
                function ($exp, $q) use ($date, $start, $end) {
                    return $exp->between($date, $start, $end);
                }
            ])
            ->all();
        $result = [];
        foreach ($summary as $row) {
            if (!isset($result[$row->date])) {
                $result[$row->date] = 0;
            }
            $result[$row->date] += $row->post_count ?: 1;
        }
        return $result;
    }

できた。

contributionmap.png

日付処理がPHPとJSで2重になっているので集計処理の際にテーブル型の配列にしてしまっても良いかもしれない。

一人もくもく会

14
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
10