LiBz Tech Blog

LiBの開発者ブログ

GASとVueでエンジニアのMuscleを可視化した

はじめに

こんにちは!前回の投稿から3ヶ月が経過して入社9ヶ月目になりましたが、今回はつまずいたシリーズはお休みしてGoogle App Script(以下、GAS)とVueで筋肉量を可視化した話を投稿させて頂きます! tech.libinc.co.jp

LiBのエンジニアでは夏に向けて?冬の間に溜め込んだ贅肉を落とすために絶賛筋トレが流行っています!

  • お昼は完全食しか食べないエンジニア
  • 会社の休憩スペースで腹筋ローラーをするエンジニア
  • ハーフマラソンに向けて走り始めたエンジニア
  • ジムに通い始めたエンジニア

などなど思い出したかのようにエンジニアが鍛え始めました。そんな筋トレブームの中、増加した筋肉量を競うために体重計(筋肉量なども計測できる)を買いました。アプリと連携して計測結果を管理することができるので、自分の携帯に保存された結果を共有していたのですが、人数も増え共有するのが辛くなってきました。そこでGASと勉強会で覚えたてのVueを使って自動化したので紹介させていただきたいと思います!

f:id:taisuke_i:20190718122943p:plain

構成

f:id:taisuke_i:20190717182429p:plain

大まかな処理の流れは以下のようになっています。

  1. 1日に1度体重計アプリのAPIサーバーから筋肉データを取得してスプレッドシートに書き込みます。
  2. GASで公開したindex.htmlファイルにアクセスが来たらスプレッドシートからデータを取得後してchart.jsでグラフ化して表示します。

必要な情報はスプレッドシートにあるのでスプレッドシートのグラフ機能作った方が簡単だし早くないか?と思いましたでしょうか? それには深い理由がありそうでなく、ただただVueもchart.jsも使ってみたかったのでこんな構成になっています! リッチなグラフの方がいいじゃないですか! ということでソースコードはここにありますが大変だったところなどをピックアップして紹介したいと思います。

体重計管理アプリのAPIサーバーから筋肉データを取得する

APIサーバーにリクエストするためのURLを組み立てます。 URLにはセッションキー、ユーザーID、カラム名をそれぞれ指定する必要があるのですがセッションキーなどコードに書きたくなかったのでユーザーの情報はスプレッドシートに保存しておき、リクエストを送る前にurl = buildURL(userIds[i][1], SESSION_KEY)の部分でユーザー数分だけURLを作成するようにしています。

fetchData.gs

function myFunction(){
    var SESSION_KEY = sessionKey(getConfigSheet());
    var userIds = getUserIds(getConfigSheet());
    var coloums =getColumns(getConfigSheet());
  
    for(var i=0;i<userIds.length;i++){
        url = buildURL(userIds[i][1], SESSION_KEY)
        results = getHealthData(url);
        if (results === undefined || results === null){throw new Error("can not fetch data");};
        dumpResults(results, userIds[i][0], getUserSheet(userIds[i][0]), coloums);
        Utilities.sleep(1000);
    };
};

その後体重計管理アプリのAPIサーバーにリクエストを送ります。GASではUrlFetchApp.fetch()を使用することでGetリクエストを送ることができます。コードではresults = getHealthData(url);の部分で以下のようなメソッドを作って使用しています。

fetchData.gs

function getHealthData(url){
    var response = UrlFetchApp.fetch(url);
    var result = response.getContentText();
    resultJson = JSON.parse(result);
    return resultJson;
};

最後にdumpResults(results, userIds[i][0], getUserSheet(userIds[i][0]), coloums);の部分で取得した情報をスプレッドシートに保存しています。差分だけ書き込めればよかったのですが既存データのチェックなどが大変そうなので取得したデータを毎回全て書き直しています。

※ 時計マークからメソッドを定期実行するように設定できます。 f:id:taisuke_i:20190718125919p:plain

webページを公開する

GASではHTMLファイルをグローバルに公開することが可能です。 公開するには以下の画面からHTMLファイルを作成します。(今回はindex.htmlという名前のファイルを作成しました。)

f:id:taisuke_i:20190717193154p:plain

その後、doGet()メソッドを定義して以下のように書きます。

doGet.gs

function doGet(){
   return HtmlService.createTemplateFromFile("公開するファイル名").evaluate();
}
function include(filename){
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
}

最後に「公開」メニューから「ウェブアプリケーションとして導入」をクリックすると先ほど作成したHTMLファイルにアクセスするためのURLが生成されます。

f:id:taisuke_i:20190717193352p:plain

index.htmlからメソッドを呼び出してスプレッドシートなどの情報にアクセス可能です。

グラフを描画する

index.htmlへのグラフ描画です! index.htmlが読み込まれる際にshowGraph.htmlの以下の部分が実行されるようになっています。

showGraph.html

created: function(){
               google.script.run
               .withSuccessHandler(this.creatChart)
               .withFailureHandler(function(arg){
               alert("データの取得に失敗しました。");
               }).getSheetData();
          }

google.script.runメソッドを使用すると非同期でメソッドを呼び出すことができます。メソッドが正常に実行された場合に、その戻り値をコールバックメソッドに渡して実行するように設定できるので、スプレッドシートのデータを取得 --> 成功したらVue側のグラフ描画処理を非同期で実行ができるようになります。 ここではgetSheetData()の実行が成功するとthis.creatChartが実行されるようになっています。

スプレッドシートのデータを受け取ったthis.creatChartでchart.jsを使用してグラフを描画しています。 chart.jsはlabels:オプションに渡した配列の順番でx軸になり、data:オプションにlabels:で指定したx軸の順番に沿うように配列でデータを渡す必要があるようでした。(他の方法を知らないだけかもですが...)

f:id:taisuke_i:20190718121856p:plain

showGraph.html

creatChart: function(args){
        var users = args.shift();
        data = [];
        data.push(this.preprocessDate(args));var weightDataList = this.createDataSet(data, 'weight');
        var muscleDataList = this.createDataSet(data, 'muscle');
        var bodyfatDataList= this.createDataSet(data, 'bodyfat');

        //グラフ描画 weightDataList
        config = {
            type: 'line',
            data: {
                labels: this.getDaysInMonth(),
                datasets: [0,1,2,3].map(function(i) {
                return { 
                    label: users[i][0],
                    data: weightDataList[i],
                    spanGaps: true,
                    fill: false
                };
                })
            }
}
                    

labels:にはthis.getDaysInMonth()メソッドで直近1ヶ月の日にちを配列で渡しているのでそれに合わせて筋肉データを加工する必要があります。 具体的には[nullnull,null,22,null,24]のように直近1ヶ月データがない日にちにはnullを入れて、データがある部分には値を入れて配列を作ります。1日に2回計測しているとデータが同じ日で重複するので重複データを取り除いたりデータを綺麗にしています。

<script>
    new Vue({
        el: '#app',
        data:{GRAPH_PERIOD: 35},
        methods: {
            //重複した日にちを除外する。日にちをUnixタイムに変換する
            preprocessDate: function(d){
                var dayBefore = null;
                d.forEach(function(value) {
                    value.forEach(function(v) {
                       formattedDate = Date.parse(new Date(v[0]).toDateString()) / 1000;
                       if (dayBefore !== null && dayBefore === formattedDate){
                         v[0] = null;
                          return;
                       };
                       v[0] = formattedDate;
                       dayBefore = formattedDate;
                   });
                });
                return d
            },
            // ユーザーごとにGRAPH_PERIOD日数分の配列を作ってデータがあれば挿入する
            createDataSet: function(d,t){
                var mDays = this.getUnixTimeInMonth();   
                var datasets = [];
                var index =  d[0][0][0].indexOf(t)
                d.forEach(function(value) {
                    value.forEach(function(v) {
                       hash = {};
                       mDays.forEach(function(h) {
                           hash[h] = null;
                       });
                       v.forEach(function(j) {
                         if(mDays.includes(j[0])){
                             hash[j[0]] = j[index];
                         };
                       });
                    datasets.push(Object.values(hash));
                    });
                  datasets
                  });
                return datasets;             
            },

以上です!

最後に

コードが長くなってくるとGASのwebエディターではコード補完が弱くて書き辛かったのですが、どうやら手元のエディターで作成 --> GitHubにpush --> GASに反映というフローもできるようなの次回GASを使うときは試せればなと思います!

それでは良い筋肉ライフを!