【Rails】個人開発のアプリにGitHub風のチャートを実装

個人開発のWebアプリまちかどルート」v6.2に、名づけて《アクティビティ・チャート》なるものを実装したときのメモです。

参考記事

こちらの記事を参考にさせていただきました。
ありがとうございます!

Railsアプリで Cal-Heatmap を表示してみる
https://qiita.com/volpe28v/items/3a5a2c05f1c0ee3fa5ad

以下、じぶんなりのアレンジも含めてメモとして残します。

view

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>  
<script src="https://cdnjs.cloudflare.com/ajax/libs/cal-heatmap/3.6.2/cal-heatmap.min.js"></script>  
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cal-heatmap/3.6.2/cal-heatmap.css" />  

レイアウトテンプレートのapplication.html.erbにこの3行を書き加えることで、今回必要なライブラリ「D3.js」と「Cal-heatmap」を読み込ませています。

<div id="heatmap"></div>  
<script>  
    var startDate = new Date();  
    startDate.setMonth(startDate.getMonth() - 5);  

    var parser = function(data) {  
        return eval("(" + data + ")");  
    };  

    var cal = new CalHeatMap();  
    cal.init({  
        itemSelector: "#heatmap",  
        data: "/api/v1/comments?user_id=<%= @user.id %>&start=<%= Time.now.ago(6.months) %>&stop=<%= Time.now %>",  
        afterLoadData: parser,  
        cellSize: 5,  
        domain: "month",  
        subDomain: "day",  
        range: 6,  
        tooltip: false,  
        start: startDate,  
        domainLabelFormat: "%b",  
        legend: [1,2,3,4],  
        weekStartOnMonday: false,  
        legendVerticalPosition: "center",  
        legendOrientation: "vertical"  
    });  
</script>  

公式のドキュメントを参考にしながらcal.init({ })内の各種オプションを指定しています。また、前述の参考記事と違うのは、とくにdataの部分です。下記のAPIに渡すパラメーターのstartstopTime.nowメソッドを使うことで、APIから取得するJSONデータの期間(ここでは6か月分)を指定しています。

API

module Resources  
  module V1  
    class Comments < Grape::API  
      resources :comments do  

        # /api/v1/comments  
        desc 'アクティビティ・チャートJSON出力'  
        params do  
          requires :user_id, type: Integer, desc: 'ユーザーID'  
        end  
        get do  
          user = User.find(params[:user_id])  
          from = params[:start]  
          to = params[:stop]  

          comments1 = Post.where(post_uid: user.uid, created_at: from..to)  
          comments2 = Comment.where(user_uid: user.uid, created_at: from..to)  
          comments1 = comments1.to_a  
          comments2 = comments2.to_a  
          comments = comments1 + comments2  
          comments.map{|c| c.created_at.to_i}.inject(Hash.new(0)){|h, tm| h[tm] += 1; h}.to_json  
        end  

      end  
    end  
  end  
end  

わたしのアプリでも、ちょうど参考記事と同じくGrapeを使ってAPIを構築しています。

参考記事と違うのはPostCommentというふたつのテーブルにある投稿日時のカラムcreated_atを抽出&結合し、アクティビティ・チャートに反映させている点です。

課題

参考記事にも書かれていますが「rails から json で返したデータをパースするメソッドを定義して afterLoadData に設定する(eval しないと json として読み込めなかったので)」とのこと。

確かにこれをしないとチャートが表示されません。

しかしながら、このせいか、ログを見るといちどに2回、APIを叩いています。

Started GET "/api/v1/comments?user_id=1&start=2018-12-18%2009:57:23%20+0900&stop=2019-06-18%2009:57:23%20+0900" for 192.168.14.1 at 2019-06-18 09:57:23 +0900  
  [1m[36mUser Load (0.1ms)[0m  [1m[34mSELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?[0m  [["id", 1], ["LIMIT", 1]]  
   app/api/resources/v1/comments.rb:12  
  [1m[36mPost Load (0.2ms)[0m  [1m[34mSELECT "posts".* FROM "posts" WHERE "posts"."post_uid" = ? AND "posts"."created_at" BETWEEN ? AND ?[0m  [["post_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]  
   app/api/resources/v1/comments.rb:18  
  [1m[36mComment Load (0.1ms)[0m  [1m[34mSELECT "comments".* FROM "comments" WHERE "comments"."user_uid" = ? AND "comments"."created_at" BETWEEN ? AND ?[0m  [["user_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]  
   app/api/resources/v1/comments.rb:19  
Started GET "/api/v1/comments?user_id=1&start=2018-12-18%2009:57:23%20+0900&stop=2019-06-18%2009:57:23%20+0900" for 192.168.14.1 at 2019-06-18 09:57:23 +0900  
  [1m[36mUser Load (0.2ms)[0m  [1m[34mSELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?[0m  [["id", 1], ["LIMIT", 1]]  
   app/api/resources/v1/comments.rb:12  
  [1m[36mPost Load (0.7ms)[0m  [1m[34mSELECT "posts".* FROM "posts" WHERE "posts"."post_uid" = ? AND "posts"."created_at" BETWEEN ? AND ?[0m  [["post_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]  
   app/api/resources/v1/comments.rb:18  
  [1m[36mComment Load (0.6ms)[0m  [1m[34mSELECT "comments".* FROM "comments" WHERE "comments"."user_uid" = ? AND "comments"."created_at" BETWEEN ? AND ?[0m  [["user_uid", "townsguild@another-guild.com"], ["created_at", "2018-12-18 09:57:23"], ["created_at", "2019-06-18 09:57:23"]]  
   app/api/resources/v1/comments.rb:19  

なんとかして1回で済むようにviewの<script>data-turbolinks-eval=falseを追記したり、いろいろ試したのですが解決できていません。

まだまだ未熟です...。
これからも精進していきたいと思います。