d3.js

「Qiitaの投稿アクティビティをGitHubのように表示するヤツ」をd3.js v4で動くようにする

More than 1 year has passed since last update.

Githubの芝っぽいものを作ってみたくなったので下記をd3.js v4で動くようにしてみた。
Qiitaの投稿アクティビティをGitHubのように表示するヤツ

やったこと

公式ドキュメントのChanges in D3 4.0の通りにしただけ。
https://github.com/d3/d3/blob/master/CHANGES.md

デモ

https://nyoronjp.github.io/qiita/20171026/

ソース

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

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

  /*
     APIから受け取った情報を元にカレンダー描画
   */
  function onSucceed(data) {
    var item_dates = {};
    $.each(data, function(idx, val) {
      var created_at = format(d3.timeDay.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;

    // 表示する日付の範囲(過去1年間)
    var rangeBegin = d3.timeDay.offset(new Date, -365);
    var rangeEnd = new Date;
    var dateRange = d3.timeDays(rangeBegin, rangeEnd);
    var monthRange = d3.timeMonths(rangeBegin, rangeEnd);

    // 矩形のX座標のオフセット値を算出する関数
    var offsetX = (function() {
      var firstYearOffset = d3.timeWeek.count(d3.timeYear(rangeBegin), rangeBegin) * -1;
      var bounderyDate = d3.timeYears(rangeBegin, rangeEnd)[0];
      var lastDayOfFirstYear = d3.timeDay.offset(bounderyDate, -1);
      var lastWeekOfFirstYear = d3.timeWeek.count(d3.timeYear(lastDayOfFirstYear), lastDayOfFirstYear);
      var lastYearOffset = d3.timeWeek.count(d3.timeYear(lastDayOfFirstYear), 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.timeDay.offset(rangeEnd, 1);
      });

      // 件数を3段階に分類するスケール関数
      var countScale = d3.scaleLinear()
                       .domain([1, d3.max(d3.values(dataset))])
                       .rangeRound([1, 3])    // 3段階で色分け
                       .clamp(true);
      // 段階ごとに色分け
      // Thanks to colorbrew (http://colorbrewer2.org/)
      var colorScale = d3.scaleOrdinal()
                       .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.timeWeek.count(d3.timeYear(d), 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.timeWeek.count(d3.timeYear(d), 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
});

差分

https://github.com/nyoronjp/nyoronjp.github.io/commit/4c556bf96d9e29cdd9e3162404096b24fa2de8c4#diff-485da9d114efb48dec0f150bafea5044