LoginSignup
1
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-10-26

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

やったこと

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

デモ

ソース

$(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
});

差分

1
1
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
1
1