search
LoginSignup
62
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

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

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

デモ

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

スクリーンショット 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;

  // 表示する日付の範囲(過去1年間)
  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面白い

参考

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
What you can do with signing up
62
Help us understand the problem. What are the problem?