Qiitaの投稿アクティビティをGitHubのように表示するヤツ


Qiita API 使ってみたかったので作ってみました。

## デモ

http://labs.snaka.info/2016/0125_qiita_calendar.html

こんな感じで表示されます。

![スクリーンショット 2016-01-27 8.34.22.png](https://qiita-image-store.s3.amazonaws.com/0/8219/cb1e2023-e0b9-a47a-eb46-7d3b9756d7cc.png "スクリーンショット 2016-01-27 8.34.22.png")


## ソースコード

久しぶりにjs書いた。あまり綺麗に書けなくてモヤモヤする。
※要 jQuery, D3.js
※手抜きで直近の100件しか取得していないので、投稿が年間100件超える場合は全て表示しきれません。

```shiiba.js
/*
   フォームの入力内容を元にAPIに問い合わせ
 */
$("#form").on("submit", function(e) {
  $("#btn-submit").prop("disabled", true);
  var user_id = $("#user_id").val()
  $.ajax({
    type: "GET",
    url: "http://qiita.com/api/v2/users/" + user_id + "/items?page=1&per_page=100",
    success: onSucceed
  });
  e.preventDefault();
});

// 日付のフォーマット
var format = d3.time.format("%Y-%m-%d");

/*
   APIから受け取った情報を元にカレンダー描画
 */
function onSucceed(data) {
  var item_dates = {};
  $.each(data, function(idx, val) {
    var created_at = format(d3.time.day.floor(new Date(val['created_at'])));
    if (created_at in item_dates)
      item_dates[created_at]++;
    else
      item_dates[created_at] = 1;
  });
  drawCalendar(item_dates);
  $("#btn-submit").prop("disabled", false);
}

/*
   カレンダーの描画
 */
var drawCalendar = (function() {
  // セルの1辺のサイズ
  var CELL_SIZE = 15;

  // ラベルの高さ
  var LABEL_HEIGHT = 11;

  // カレンダ表示のマージン
  var MARGIN_LEFT = 25;
  var MARGIN_TOP = 15;

  // 表示する日付の範囲(過去１年間)
  var rangeBegin = d3.time.day.offset(new Date, -365);
  var rangeEnd = new Date;
  var dateRange = d3.time.days(rangeBegin, rangeEnd);
  var monthRange = d3.time.months(rangeBegin, rangeEnd);

  // 矩形のX座標のオフセット値を算出する関数
  var offsetX = (function() {
    var firstYearOffset = d3.time.weekOfYear(rangeBegin) * -1;
    var bounderyDate = d3.time.years(rangeBegin, rangeEnd)[0];
    var lastDayOfFirstYear = d3.time.day.offset(bounderyDate, -1);
    var lastWeekOfFirstYear = d3.time.weekOfYear(lastDayOfFirstYear);
    var lastYearOffset = d3.time.weekOfYear(lastDayOfFirstYear) + firstYearOffset;

    return function(sourceDate) {
      if (sourceDate.getFullYear() == rangeBegin.getFullYear())
        return firstYearOffset;
      return lastYearOffset;
    }
  })();

  // メイン処理:カレンダー描画
  return function(originalDataset) {

    // Objectをkeyでフィルタする関数
    function objectFilter(obj, predicate) {
      var result = {}, key;
      for (key in obj) {
        if (obj.hasOwnProperty(key) && predicate(key, obj[key])) {
          result[key] = obj[key];
        }
      }
      return result;
    }

    var dataset = objectFilter(originalDataset, function(k, v) {
      var aDay = new Date(k);
      return rangeBegin <= aDay && aDay <= d3.time.day.offset(rangeEnd, 1);
    });

    // 件数を3段階に分類するスケール関数
    var countScale = d3.scale.linear()
                     .domain([1, d3.max(d3.values(dataset))])
                     .rangeRound([1, 3])    // 3段階で色分け
                     .clamp(true);
    // 段階ごとに色分け
    // Thanks to colorbrew (http://colorbrewer2.org/)
    var colorScale = d3.scale.ordinal()
                     .domain([1, 2, 3])
                     .range(["#f7fcb9","#addd8e","#31a354"]);
    var color = function(f) { return colorScale(countScale(f)); }

    // svg要素を選択
    var svg = d3.select(".weed");
    svg.selectAll("*").remove();
    svg.append("g");

    // 日毎の矩形を生成
    var rect = svg.selectAll(".day")
              .data(dateRange)
              .enter()
              .append("rect")
                .attr("class", "day")
                .attr("width", CELL_SIZE - 1)
                .attr("height", CELL_SIZE - 1)
                .attr("x", function(d){ return (d3.time.weekOfYear(d) + offsetX(d)) * CELL_SIZE + MARGIN_LEFT; })
                .attr("y", function(d){ return d.getDay() * CELL_SIZE + MARGIN_TOP; })
                .attr("fill", "#e6e6e6")
              .datum(format);

    // 投稿のあった日のツールチップと背景色を設定
    rect.filter(function(d){ return d in dataset; })
        .attr("fill", function(d){ return colorScale(countScale(dataset[d])); })
        .append("title")
          .text(function(d){ return d + " (投稿:" + dataset[d] + "件)"; });

    // 曜日のラベル
    dayLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
    svg.selectAll(".dayLabel")
       .data(d3.range(7))
       .enter()
       .append("text")
         .attr("class", "dayLabel")
         .attr("x", 0)
         .attr("y", function(d){ return d * CELL_SIZE + LABEL_HEIGHT + MARGIN_TOP; })
         .attr("font-size", LABEL_HEIGHT)
         .attr("fill", "gray")
         .text(function(d){ return dayLabels[d]; });

    // 月のラベル
    monthLabels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
    svg.selectAll(".monthLabel")
      .data(monthRange)
      .enter()
      .append("text")
        .attr("class", "monthLabel")
        .attr("x", function(d){ return (d3.time.weekOfYear(d) + offsetX(d)) * CELL_SIZE + MARGIN_LEFT; })
        .attr("y", LABEL_HEIGHT)
        .attr("font-size", LABEL_HEIGHT)
        .attr("fill", "gray")
        .text(function(d){ return monthLabels[d.getMonth()]; });
  }
})(); // drawCalendar
```

## 得られたもの

* GitHubのあの草はD3.jsで実装されている
* D3.jsによるビジュアライズは(主に)SVGを使用している
* D3.js面白い


## 参考

* [【D3.js + node.js】 ブログのデータをGithub風のカレンダーに表示する | #GUNMAGISGEEK](http://shimz.me/blog/node-js/2975)
* [Calendar View](http://bl.ocks.org/mbostock/4063318)
* [Home · mbostock/d3 Wiki](https://github.com/mbostock/d3/wiki)